Why Asynchronous Delegates Are Your Friend - ' Polling for Completion ' (
Page 3 of 4 )
A delegate's BeginInvoke method has the same parameters as the delegate's Invoke method. This makes asynchronous execution the easy way to pass any number of parameters to a thread procedure.
(A delegate's BeginInvoke method also has two extra parameters that are used for an optional callback when the method terminates; I cover these required parameters in the Callback on completion section, below, but it's always perfectly safe to just pass null to each.)
Every call to BeginInvoke, no matter which delegate you're invoking, returns an IAsynchResult that you must save and pass to the same delegate's EndInvoke method.
The EndInvoke method takes a single IAsynchResult parameter; waits for the call to complete; and returns the delegate result, if any. Note that EndInvoke does not return an object that you have to cast to the right type. Rather, EndInvoke has the same result type as the delegate itself: a delegate that 'returns' void has an EndInvoke method that 'returns' void; a delegate that returns a string has an EndInvoke method that returns a string; and so on.
There are several different ways to detect that an asynchronous call has completed, but you must pass the IAsyncResult that you get from ThisDelegate.BeginInvoke to ThisDelegate.EndInvoke. Yes, even if you already know the delegate has returned, and even if EndInvoke does not return a result.
Thus, the simplest asynchronous scenario is something like
IAsynchResult Asynch =
ReadFileDelegate.BeginInvoke(Filename, null, null);
//
// Do as much as you can until you need
// the file contents: EndInvoke() blocks.
//
string FileContents = ReadFileDelegate.EndInvoke(Asynch);
Calling EndInvoke like this is not unlike calling Join on a thread: you don't need to know the current state of the operation, you just block until it's completed.
Asynchronous execution of delegates in a thread pool thread is not the same thing as sending a control a message asking it to execute a delegate in the control's thread! A (System.Windows.Forms) Control has a BeginInvoke method that takes a delegate, and an EndInvoke method that takes the IAsyncResult that BeginInvoke returned. These Control methods act differently than the delegate's own BeginInvoke and EndInvoke methods. The Control methods don't take callback parameters, and it is safe to not EndInvoke an IAsyncResult that you get from a Control.BeginInvoke call.
Polling for Completion
It's not always a good idea to block the application's main thread. It's okay in a command line utility that simply runs to completion with no user intervention, and it's okay in a web server where each incoming request runs in its own thread, but it's generally not okay in a GUI app, where blocking the main thread means that the app is not responding to mouse clicks and key presses.
If you initiate a lengthy asynchronous call from the main GUI thread, you might poll the IAsyncResult.IsCompleted property and call Application.DoEvents until the delegate returns:
IAsynchResult Asynch = ReadBigFileDelegate.BeginInvoke(Filename, null, null);
//
// Do something while waiting for file IO
//
while (! Asynch.IsCompleted) // we need the file contents now
Application.DoEvents(); // handle events until delegate returns
string FileContents = ReadBigFileDelegate.EndInvoke(Asynch);
A better design is usually for the ReadBigFileDelegate to run to completion, then use Control.BeginInvoke to have the GUI thread run a delegate that will do ReadBigFileDelegate. EndInvoke and display the fetched data.
Of course, while file IO is slow, it's not all that slow, and you can usually get away with reading a small (one or two kilobyte) file during an event handler. As a rule of thumb, if you can do something synchronously without impacting GUI responsiveness, the asynchronous version can certainly block on EndInvoke without impacting GUI responsiveness.
Waiting for Completion
If the asynchronous delegate has any side effects, other threads may want to wait for it to complete before doing anything that depends on those side effects. These threads can use WaitHandle methods like the IAsyncResult.AsyncWaitHandle.WaitOne instance method or the static WaitHandle methods WaitAll and WaitAny to wait until the IAsyncResult.AsyncWaitHandle event is signaled.
While you can call BeginInvoke in one thread and call EndInvoke in a different thread, this still allows only one thread to wait for the delegate. The AsyncWaitHandle property returns a ManualResetEvent that blocks all waiting threads until the event is signaled, and then lets all threads through after the event is signaled. That is, the AsyncWaitHandle property allows any number of threads to wait for the delegate. (However, you should be sure to only call EndInvoke once.)