Introduction
There are tons of ways on making asynchronous calls in .NET, and you should choose which one to use based on different scenarios. The scenario I want to cover here is how to deal with a situation where you need to make multiple asynchronous calls, and wait until all of them are completed in order to move on to the next step.
This is a typical situation in which you need to make multiple database calls, and wait for all the result sets to come back before continuing your process.
The Problem
In this example, you have a method SomeRandomNumber
, which takes some time to execute, and will return an integer value. You will have to make 3 asynchronous calls to this method and wait until all of them are completed.
private static int SomeRandomNumber(int waitTime)
{
Thread.Sleep(waitTime);
return rnd.Next();
}
One of the most common solutions is to put all asynchronous calls to the ThreadPool
, and you will need a loop to check if they are all completed, based on some global bool flag in your instance. First of all, the code is not clean, and this imperative style is hard to control in multiple threading situation. You can't be sure no one else will change your flag!
private int? threadOneResult;
private int? threadTwoResult;
private int? threadThreeResult;
public void AsyncCallsWithThreadPool()
{
ThreadPool.QueueUserWorkItem(ThreadOne, 1000);
ThreadPool.QueueUserWorkItem(ThreadTwo, 5000);
ThreadPool.QueueUserWorkItem(ThreadThree, 10000);
while (threadOneResult == null ||
threadTwoResult == null ||
threadThreeResult == null)
{
Thread.Sleep(100);
}
Console.WriteLine(
String.Format("Result: {0}\t{1}\t{2}",
threadOneResult,
threadTwoResult,
threadThreeResult));
}
ThreadOne
, ThreadTwo
, and ThreadThree
put back the result to the global int?
variables. After all three threads are completed, the above method will get out of the while
loop.
private void ThreadOne(object state)
{
threadOneResult = SomeRandomNumber((int)state);
}
private void ThreadTwo(object state)
{
threadTwoResult = SomeRandomNumber((int)state);
}
private void ThreadThree(object state)
{
threadThreeResult = SomeRandomNumber((int)state);
}
The Solution
The methods BeginInvoke
and EndInvoke
will do all the work for you. BeginInvoke
will process your asynchronous call in a new thread. EndInvoke
will return the result from that thread. If the thread is not completed when you are calling EndInvoke
, it will wait. You don't have to implement your wait
loop in the main thread. The status of each asynchronous operation is saved in the IAsyncResult
object, so one delegate can handle multiple invokes.
This approach can eliminate all the unnecessary global variables, so your asynchronous logic does not need to rely on the state in this instance. This looks clean. The most important thing is that this keeps your threading structure simple.
public void AsyncCallsInDelegate()
{
GetNumber deleg = new GetNumber(SomeRandomNumber);
IAsyncResult rst1 = deleg.BeginInvoke(1000, null, null);
IAsyncResult rst2 = deleg.BeginInvoke(5000, null, null);
IAsyncResult rst3 = deleg.BeginInvoke(10000, null, null);
Console.WriteLine(
String.Format("Result: {0}\t{1}\t{2}",
deleg.EndInvoke(rst1),
deleg.EndInvoke(rst2),
deleg.EndInvoke(rst3)));
}
Life is getting better in .NET 3.5. You can use the generic delegate Func<int>
to save the work of declaring a new delegate:
public void AsyncCallsInGenericDelege()
{
Func<int> deleg = new Func<int>(SomeRandomNumber);
IAsyncResult rst1 = deleg.BeginInvoke(1000, null, null);
IAsyncResult rst2 = deleg.BeginInvoke(5000, null, null);
IAsyncResult rst3 = deleg.BeginInvoke(10000, null, null);
Console.WriteLine(
String.Format("Result: {0}\t{1}\t{2}",
deleg.EndInvoke(rst1),
deleg.EndInvoke(rst2),
deleg.EndInvoke(rst3)));
}
History
- 8/8/2010 - First release on CodeProject