Fri, October 1, 2004, 05:30 AM under
Whidbey |
VisualStudio
Last time we looked at
Control.Invoke on both CF and full framework. Assuming you have read it, let’s look at VS2005.
On the desktop, if you forget to use Control.Invoke, you now get an InvalidOperationException with the Message: "Illegal cross-thread operation: Control 'SomeControlName' accessed from a thread other than the thread it was created on." That is cool, and the stack trace will of course point you to the culprit. Note this only works when debugging in the IDE, and is not available to CF projects. Instead, with Smart Device projects, you get the (catchable) System.NotSupportedException (with the Beta 1 bits).
CF 2.0 brings parity with the desktop; all 3 limitations are removed, so you don't need the ThreadPool or the Queue or the need to cast:
// ...or any other type/params you want
delegate void SomeCustomDelegate(object o);
// This method runs on a non-GUI thread e.g. Threading.Timer
internal void OnNonGuiThread(Object o){
// if you have more than one argument just add it to the array
object[] arr = {o};
// assuming all this code is in a form
this.BeginInvoke(new SomeCustomDelegate(UpdateBox), arr);
}
// This method runs on GUI thread
private void UpdateBox(Object o
/*other arguments as defined by SomeCustomDelegate*/){
// TODO use o and other arguments
this.Text = o.ToString();
}
Fri, October 1, 2004, 05:24 AM under
dotNET
It should be a
well known fact by now that, when in need to
updade controls created on the GUI thread, you must use
Control.Invoke (call the thread-safe static Invoke method on any control, e.g. the Form). It should be but, judging at how often this comes up in the newsgroups, it's not.
The issue and solution are the same for both full and compact frameworks; the difference is that, although the desktop seems to be forgiving (sometimes updating from non-GUI thread works), the CF certainly isn't:
every time you try it, the result is an application that is hung/frozen/locked up. If you see this on your device, start looking for the said problem.
Of course, like almost every other area of the CF, Control.Invoke support is limited. It is limited in 3 areas:
1. You cannot pass any delegate you want to Invoke; rather, you can only use the EventHandler. So, if you were hoping on using the efficient
MethodInvoker or one of your own, forget about it.
2. Invoke cannot be used to pass arguments. So, you can marshal control from a thread to the GUI thread, but you cannot pass any parameters in the same call.
3. BeginInvoke is not supported. So, you can only do it synchronously (your non-GUI thread does not return until the Invoked method itself completes).
A solution to the 2nd limitation is to write code like this:
Private mDataQue As New Queue
Private mUpdateDel As New EventHandler(AddressOf UpdateBox)
' This method runs on a non-GUI thread e.g. Threading.Timer
Friend Sub OnNonGuiThread(ByVal o As Object)
' if you have more than one argument
' create an object or structure
' that holds the data you want to pass to the GUI thread
SyncLock mDataQue.SyncRoot
mDataQue.Enqueue(o)
End SyncLock
' assuming all this code is in a form
Me.Invoke(mUpdateDel)
End Sub
' This method runs on GUI thread
Private Sub UpdateBox(ByVal sender As Object, ByVal e As EventArgs)
Dim o As Object
SyncLock mDataQue.SyncRoot
If mDataQue.Count > 0 Then
o = mDataQue.Dequeue()
End If
End SyncLock
' TODO use o
' cast o to your object/structure and
' use it to update the GUI
End Sub
The 3rd limitation can be overome simply by using the threadpool. Extend the previous solution with:
' NOW, *this* is the method that runs on non-GUI thread
' e.g. Threading.Timer
' The original method is now just a helper
Public Sub OnNonGuiThread2(ByVal o As Object)
ThreadPool.QueueUserWorkItem(AddressOf OnNonGuiThread,o)
End Sub
We look at what Whidbey brings to the table
next time.