SUMMARY
The Microsoft .NET Framework makes it easy to call
functions asynchronously. Calling functions asynchronously causes the system to
execute them in the background on a secondary thread while the calling function
continues to do other work. In a typical (synchronous) function call, the
function is executed right away on the same thread that made the call. The
calling function waits for the call to complete and receives the results of the
call before continuing. By contrast, when you make an asynchronous call, you
retrieve the results of the asynchronous call later. This article demonstrates
how to do this by using Visual C#.
back to the top
Requirements
The following list outlines the recommended hardware,
software, network infrastructure, and service packs that are required:
- Microsoft Windows 2000 or Microsoft Windows XP or Microsoft Windows Server 2003
- Visual Studio .NET or Visual Studio 2005
This article assumes that you are familiar with the following
topics:
- Calling methods in Visual C#
- How to use delegates
back to the top
How To Make Asynchronous Calls
Asynchronous calls are made by using delegates. A delegate is an
object that wraps a function. Delegates provide a synchronous function and also
provide methods for calling the wrapped function asynchronously. Those methods
are
BeginInvoke() and
EndInvoke(). The parameter lists of these methods vary depending on the
signature of the function that the delegate wraps. Note that the Visual Studio
.NET IntelliSense feature does not display
BeginInvoke() and
EndInvoke(), so you do not see them appear in the function lists as you type.
BeginInvoke() is used to initiate the asynchronous call. It has the same
parameters as the wrapped function, plus two additional parameters that will be
described later in this article.
BeginInvoke() returns immediately and does not wait for the asynchronous call
to complete.
BeginInvoke() returns an
IAsyncResult object.
The
EndInvoke() function is used to retrieve the results of the asynchronous
call. It can be called anytime after
BeginInvoke(). If the asynchronous call has not completed yet,
EndInvoke() blocks until it completes. The parameters of the
EndInvoke() function includes the
out and
ref parameters that the wrapped function has, plus
the
IAsyncResult object that is returned by
BeginInvoke().
The following is an example of a delegate and its
BeginInvoke() and
EndInvoke() methods:
// The following delegate
delegate string MethodDelegate(int iCallTime, out int iExecThread) ;
// will have the following BeginInvoke() and EndInvoke methods:
IAsyncResult MethodDelegate.BeginInvoke(int iCallTime, out int iExecThread, AsyncCallback cb, object AsyncState);
string MethodDelegate.EndInvoke (out int iExecThread, IAsyncResult ar) ;
There are four common ways to use
BeginInvoke() and
EndInvoke() to make asynchronous calls. After you call
BeginInvoke(), you can:
- Optionally do some other work and then use EndInvoke().
- Obtain a WaitHandle that is provided by the IAsyncResult object, use its WaitOne method to block until the WaitHandle is signaled, and then call EndInvoke().
- Poll the IAsyncResult object to determine when the asynchronous call has completed, and
then call EndInvoke().
- Have the system call a callback function that you specify.
This callback function calls EndInvoke() and processes the results of the asynchronous call when it
completes.
The following code samples demonstrate these call patterns and
contrast them with making a synchronous call by using the following function:
string LongRunningMethod (int iCallTime, out int iExecThread)
{
Thread.Sleep (iCallTime) ;
iExecThread = AppDomain.GetCurrentThreadId ();
return "MyCallTime was " + iCallTime.ToString() ;
}
LongRunningMethod() simulates a function that runs for long time by sleeping. It
returns the sleep time and the ID of the thread that executes it. If you call
it asynchronously, you find that the thread ID of the executing thread is
different from that of the calling thread.
The first step is to
define the delegate that wraps the function:
delegate string MethodDelegate(int iCallTime, out int iExecThread) ;
Sample 1: Calling A Method Synchronously
This sample demonstrates how to call
LongRunningMethod() synchronously by using a
MethodDelegate delegate. The other samples contrast this by making calls
asynchronously.
- Start Microsoft Visual Studio .NET or Microsoft Visual Studio 2005.
- Create a new Visual C# Console Application project named
AsyncDemo.
- Add a class named AsyncDemo that is defined as follows to
the project in a new .cs file:
using System;
using System.Threading ;
using System.Windows.Forms ;
public class AsyncDemo
{
string LongRunningMethod (int iCallTime, out int iExecThread)
{
Thread.Sleep (iCallTime) ;
iExecThread = AppDomain.GetCurrentThreadId ();
return "MyCallTime was " + iCallTime.ToString() ;
}
delegate string MethodDelegate(int iCallTime, out int iExecThread) ;
public void DemoSyncCall()
{
string s ;
int iExecThread;
// Create an instance of a delegate that wraps LongRunningMethod.
MethodDelegate dlgt = new MethodDelegate (this.LongRunningMethod) ;
// Call LongRunningMethod using the delegate.
s = dlgt(3000, out iExecThread);
MessageBox.Show (string.Format ("The delegate call returned the string: \"{0}\",
and the thread ID {1}", s, iExecThread.ToString() ) );
}
}
Later, this class demonstrates how to make asynchronous calls.
Initially, however, it only contains the DemoSyncCall() method, which demonstrates how to call the delegate
synchronously. - Add the following code in the body of the Main function that Visual Studio automatically creates in your
project:
static void Main(string[] args)
{
AsyncDemo ad = new AsyncDemo () ;
ad.DemoSyncCall() ;
}
- Press CTRL+F5 to run your application.
back to the top
Sample 2: Calling A Method Asynchronously by Using the EndInvoke() Call Pattern
In this section, the sample invokes the
same method asynchronously. The call pattern that is used is to call
BeginInvoke, do some work on the main thread, and then call
EndInvoke(). Note that
EndInvoke() does not return until the asynchronous call has completed. This
call pattern is useful when you want to have the calling thread do work at the
same time that the asynchronous call is executing. Having work occur at the
same time can improve the performance of many applications. Common tasks to run
asynchronously in this way are file or network operations.
- Add a method named DemoEndInvoke() to the AsyncDemo class. The DemoEndInvoke function demonstrates how to call the delegate asynchronously.
public void DemoEndInvoke()
{
MethodDelegate dlgt = new MethodDelegate (this.LongRunningMethod) ;
string s ;
int iExecThread;
// Initiate the asynchronous call.
IAsyncResult ar = dlgt.BeginInvoke(3000, out iExecThread, null, null);
// Do some useful work here. This would be work you want to have
// run at the same time as the asynchronous call.
// Retrieve the results of the asynchronous call.
s = dlgt.EndInvoke (out iExecThread, ar) ;
MessageBox.Show (string.Format ("The delegate call returned the string: \"{0}\",
and the number {1}", s, iExecThread.ToString() ) );
}
- Edit the source code for Main so that it contains the following code:
static void Main(string[] args)
{
AsyncDemo ad = new AsyncDemo () ;
ad.DemoEndInvoke() ;
}
- Press CTRL+F5 to run your application.
back to the top
Sample 3: Calling A Method Asynchronously And Using A WaitHandle To Wait For The Call To Complete
In this section, the sample calls the method asynchronously
and waits for a
WaitHandle before it calls
EndInvoke(). The
IAsyncResult that is returned by
BeginInvoke() has an
AsyncWaitHandle property. This property returns a
WaitHandle that is signaled when the asynchronous call completes. Waiting on
a
WaitHandle is a common thread synchronization technique. The calling thread
waits on the
WaitHandle by using the
WaitOne() method of the
WaitHandle.
WaitOne() blocks until the
WaitHandle is signaled. When
WaitOne() returns, you can do some additional work before you call
EndInvoke(). As in the previous sample, this technique is useful for
executing file or network operations that would otherwise block the calling
main thread.
- Add a function named DemoWaitHandle() to the AsyncDemo class. The DemoWaitHandle() function demonstrates how to call the delegate asynchronously.
public void DemoWaitHandle ()
{
string s ;
int iExecThread;
MethodDelegate dlgt = new MethodDelegate (this.LongRunningMethod) ;
// Initiate the asynchronous call.
IAsyncResult ar = dlgt.BeginInvoke(3000, out iExecThread, null, null);
// Do some useful work here. This would be work you want to have
// run at the same time as the asynchronous call.
// Wait for the WaitHandle to become signaled.
ar.AsyncWaitHandle.WaitOne() ;
// Get the results of the asynchronous call.
s = dlgt.EndInvoke (out iExecThread, ar) ;
MessageBox.Show (string.Format ("The delegate call returned the string: \"{0}\",
and the number {1}", s, iExecThread.ToString() ) );
}
- Edit the source code for Main so that it contains the following code:
static void Main(string[] args)
{
AsyncDemo ad = new AsyncDemo () ;
ad.DemoWaitHandle () ;
}
- Press CTRL+F5 to run your application.
back to the top
Sample 4: Calling A Method Asynchronously by Using the Polling Call Pattern
In this section, the sample polls the
IAsyncResult object to find out when the asynchronous call has completed. The
IAsyncResult object that is returned by
BeginInvoke() has an
IsCompleted property that returns
True after the asynchronous call completes. You can then call
EndInvoke(). This call pattern is useful if your application does ongoing
work that you do not want to have blocked by a long-running function call. A
Microsoft Windows application is an example of this. The main thread of the
Windows application can continue to handle user input while an asynchronous
call executes. It can periodically check
IsCompleted to see if the call has completed. It calls
EndInvoke when
IsCompleted returns
True. Because
EndInvoke() blocks until the asynchronous operation is complete, the
application does not call it until it knows that the operation is complete.
- Add a function named DemoPolling() to the AsyncDemo class. The DemoPolling() function demonstrates how to call the delegate asynchronously and
use polling to see if the process is complete.
public void DemoPolling()
{
MethodDelegate dlgt = new MethodDelegate (this.LongRunningMethod) ;
string s ;
int iExecThread;
// Initiate the asynchronous call.
IAsyncResult ar = dlgt.BeginInvoke(3000, out iExecThread, null, null);
// Poll IAsyncResult.IsCompleted
while(ar.IsCompleted == false)
{
Thread.Sleep (10) ; // pretend to so some useful work
}
s = dlgt.EndInvoke (out iExecThread, ar) ;
MessageBox.Show (string.Format ("The delegate call returned the string: \"{0}\",
and the number {1}", s, iExecThread.ToString() ) );
}
- Edit the source code for Main. Replace the content of the function with the following code:
static void Main(string[] args)
{
AsyncDemo ad = new AsyncDemo () ;
ad.DemoPolling () ;
}
- Press CTRL+F5 to run your application.
back to the top
Sample 5: Executing a Callback When an Asynchronous Method Completes
In this section, the sample provides a callback delegate to the
BeginInvoke() function that the system executes when the asynchronous call
completes. The callback calls
EndInvoke() and processes the results of the asynchronous call. This call
pattern is useful if the thread that initiates the asynchronous call does not
need to process the results of the call. The system invokes the callback on a
thread other than the initiating thread when the asynchronous call completes.
To use this call pattern, you must pass a delegate of type
AsyncCallback as the second-to-last parameter of the
BeginInvoke() function.
BeginInvoke() also has a final parameter of type
object into which you can pass any object. This object is available to
your callback function when it is invoked. One important use for this parameter
is to pass the delegate that is used to initiate the call. The callback
function can then use the
EndInvoke() function of that delegate to complete the call. This call pattern
is demonstrated below.
- Add a two methods named DemoCallback() and MyAsyncCallback() to the AsyncDemo class. The DemoCallback() method demonstrates how to call the delegate asynchronously. It
uses a delegate to wrap the MyAsyncCallback() method, which the system calls when the asynchronous operation
completes. MyAsyncCallback() calls EndInvoke().
public void DemoCallback()
{
MethodDelegate dlgt = new MethodDelegate (this.LongRunningMethod) ;
string s ;
int iExecThread;
// Create the callback delegate.
AsyncCallback cb = new AsyncCallback(MyAsyncCallback);
// Initiate the Asynchronous call passing in the callback delegate
// and the delegate object used to initiate the call.
IAsyncResult ar = dlgt.BeginInvoke(3000, out iExecThread, cb, dlgt);
}
public void MyAsyncCallback(IAsyncResult ar)
{
string s ;
int iExecThread ;
// Because you passed your original delegate in the asyncState parameter
// of the Begin call, you can get it back here to complete the call.
MethodDelegate dlgt = (MethodDelegate) ar.AsyncState;
// Complete the call.
s = dlgt.EndInvoke (out iExecThread, ar) ;
MessageBox.Show (string.Format ("The delegate call returned the string: \"{0}\",
and the number {1}", s, iExecThread.ToString() ) );
}
- Edit the source code for Main. Replace the content of the function with the following code:
static void Main(string[] args)
{
AsyncDemo ad = new AsyncDemo () ;
ad.DemoCallback() ;
}
- Press CTRL+F5 to run your application.
back to the top