Google
 

Friday, May 25, 2007

What's up with BeginInvoke?

REF :http://www.codeproject.com/csharp/begininvoke.asp

What does BeginInvoke do?

According to MSDN, Control.BeginInvoke "Executes the specified delegate asynchronously on the thread that the control's underlying handle was created on". It basically takes a delegate and runs it on the thread that created the control on which you called BeginInvoke.

Message queue and Message pumping

  1. Every Windows application you create is provided with a thread on startup. It's this thread that calls the Main method of your application.
  2. For a console application, you can write Main to accept user input and process them.
  3. For a GUI application, things are radically different. GUI applications are event based, which means that there needs to be some entity to process and fire events. Windows manages this by creating a message queue for your application. All UI related actions get translated to messages that get posted to this message queue 1. Now, you need someone to read the messages from the queue and call appropriate event handlers. That's what a message pump is for. It basically is a loop that waits for someone to post a message to the queue. Once someone does, it dequeues it and calls the associated event handler.
  4. In .NET GUI applications, the Application.Run method takes care of message pumping.
  5. As an aside, modal dialogs (shown using Form.ShowDialog) have their own message pump whereas modeless dialogs (shown using Form.Show) don't. Which means that you can call Form.ShowDialog from any thread but Form.Show requires the calling thread to be running a message pump. Note that if you call Form.Show from a UI event handler, both the new form and the current form will be sharing the same message pump, which means that if one form is stuck executing one of the event handlers, the other one won't be usable too.

The One Rule

  1. One of the cardinal rules of Windows GUI programming is that only the thread that created a control can access and/or modify its contents (except for a few documented exceptions). Try doing it from any other thread and you'll get unpredictable behavior ranging from deadlock, to exceptions to a half updated UI. The right way then to update a control from another thread is to post an appropriate message to the application message queue. When the message pump gets around to executing that message, the control will get updated, on the same thread that created it (remember, the message pump runs on the main thread).
  2. There are two fundamental Win32 API calls to access and/or modify a control, SendMessage and PostMessage. There is a big difference in the way the two execute.
    1. The major difference is that SendMessage blocks the caller till the message gets processed by the message pump whereas PostMessage returns immediately.
    2. The subtle but important difference is that messages sent using SendMessage aren't queued in the message queue whereas PostMessage messages are.
  3. SendMessage messages are directly "sent" to the message pump. The message pump retrieves and processes messages sent using SendMessage before looking into those in the message queue.
  4. Effectively, there are then two queues, one for SendMessage messages and one for PostMessage messages (which is what we call the message queue). The message pump processes all messages in the first queue before starting with the second.
  5. An interesting observation is that if code does a SendMessage from within the message pumping thread, the window procedure gets called directly, that is, it doesn't go through the message pump.

Why The One Rule Exists

  1. In .NET, property/method calls on a Control object translate to SendMessage calls, so you can easily see how it can turn nasty. PostMessage posts a message to the queue and returns immediately, so there is no chance of deadlock.
  2. There is one other issue with using SendMessage that is not obvious at all. SendMessage, while blocking the current thread, continues to process messages that are sent to the message pump using SendMessage or one of the functions that send nonqueued[ ^] messages. This means that your code must be prepared to handle incoming messages when blocked on a SendMessage call.
  3. To avoid such problems, Windows provides the SendMessageTimeout API function, which when called with the right parameters, will prevent pumping of nonqueued messages when blocked on sending a message.
  4. However, all .NET UI controls use SendMessage, so you can't avoid the problem, unless you are planning on doing P/Invoke.

Why and when to call BeginInvoke

  1. BeginInvoke essentially does a PostMessage.
  2. Whenever you want to update a control from a thread that didn't create it, instead of directly calling the method/property to update it, you need to wrap it in a BeginInvoke call.
  3. According to MSDN: "There are four methods on a control that are safe to call from any thread: Invoke, BeginInvoke, EndInvoke, and CreateGraphics. For all other method calls, you should use one of the invoke methods to marshal the call to the control's thread".
  4. the Control class provides one property and two methods:
    • InvokeRequired: This bool property returns true if the thread on which this property is called is not the thread that created this control. Basically, if InvokeRequired returns true, you need to call one of the two Invoke methods.
    • BeginInvoke: This is a functionally similar to the PostMessage API function. It posts a message to the queue and returns immediately without waiting for the message to be processed. BeginInvoke returns an IAsyncResult, just like the BeginInvoke method on any delegate. And you can use IAsyncResult to wait for the message to be processed, just as usual. And you can call EndInvoke to get return values or out parameter values, as usual.
    • Invoke: This is like the SendMessage API function in that it waits till the message gets processed, but it does not do a SendMessage internally, it also does a PostMessage . The difference is that it waits till the delegate is executed on the UI thread before returning. So while there is a chance for the deadlock problem to occur, you can be sure that the other problems associated with SendMessage won't happen. Invoke returns the value that the function wrapped by the delegate returned.
  5.  The BCL provides a MethodInvoker delegate, which you can use if your wrapped function takes no parameters and returns void. You can also reuse the EventHandler delegate in case your wrapped function's signature matches it. The MSDN documentation says using these delegates instead of our own custom delegates will result in faster execution.
  6. Both BeginInvoke and Invoke check if they are called on the correct thread (the thread that created the control) and if so, directly update the control instead of doing the PostMessage thing. Apart from performance benefits, it also prevents deadlock if you call Invoke from within a method already running on the UI thread.

Invoke and BeginInvoke

  1. There are a few gotcha's with BeginInvoke though.
    • If the function you are calling via BeginInvoke accesses shared state (state shared between the UI thread and other threads), you are in trouble. The state might change between the time you called BeginInvoke and when the wrapped function actually executes, leading to hard to find timing problems.
    • If you are passing reference parameters to the function called via BeginInvoke, then you must make sure that no one else modifies the passed object before the function completes. Usually, people clone the object before passing it to BeginInvoke, which avoids the problem altogether.
  2. Note that the above points are valid for any function that you run as a thread. They're not so obvious when using BeginInvoke, because BeginInvoke doesn't actually create a thread and instead runs the wrapped function on an already existing thread (the UI thread). It still means that there are two threads, so you want to take the same care protecting your shared variables.

A warning

  1. Control.BeginInvoke, which is what we have been discussing so far, works slightly differently from Delegate.BeginInvoke.
  2. Delegate.BeginInvoke grabs a threadpool thread and executes the passed delegate on that thread.
  3. Control.BeginInvoke does not use a threadpool thread, it does a PostMessage to the target window handle and returns.
  4. This is crucial because if it uses threads, then there is no guarantee to the order in which messages are posted and processed by the application.
  5. Also, unlike Delegate.BeginInvoke, Control.BeginInvoke doesn't require every call to BeginInvoke to be matched by an EndInvoke. Of course, if you are using the return value of the method and/or out or ref values, then you need to call it anyway.




--
Happy day, happy life!

No comments: