Index
- Introduction
- Using Asynchronous Code Blocks
- Handling Exceptions in Asynchronous Code Blocks
- WinForms and Asynchronous Code Blocks
- Points of Interest
- Software Usage
- History
1. Introduction
Asynchronous Code Blocks, shortly called as ACB hereafter in this article, is a technique and a companion library for C# 2.0 applications, to run portions of a method code asynchronously without explicitly using threads, delegate type declarations, thread handler methods, synchronization objects, and all those complex stuff associated with asynchronous multithreaded programming.
Below is the summary of the features offered by the ACB library:
- Run portions of the method code defined using anonymous methods asynchronously.
- Asynchronous execution of the ACB can be scheduled on an AppDomain-wide ManagedIOCP ThreadPool, or a user-created ManagedIOCP ThreadPool instance.
- Wait on completion of the ACB infinitely, or for specified time (milliseconds).
- Wait on completion of execution of all the ACBs in a list of ACBs. The features 3 and 4 allow an application to wait on all the pending ACBs during its closure.
- Wait on completion of execution of any one of the ACBs in a list of ACBs.
- Get notified about the execution completion of an asynchronous code block using a delegate.
- Make the execution of an ACB dependent on the completion of execution of another ACB. For instance, if ACB Y is dependent on ACB X, ACB Y will start executing only after ACB X has completed its execution.
- Make the execution of an ACB dependent on the completion of execution of all the ACBs in a list of ACBs.
As you already observed, ACB depends on �Anonymous Methods� in C# 2.0 and the ManagedIOCP based Task Framework and the ThreadPool class from the Sonic.Net library. The Sonic.Net library is my own library that I created as part of my ManagedIOCP articles on CodeProject. Below are the links to my articles on �Anonymous Methods� and �ManagedIOCP�.
- Inside C# 2.0 Anonymous Methods: This article explains the inner workings of C# 2.0 Anonymous Methods.
- Managed I/O Completion Ports (IOCP): This articles explains ManagedIOCP in detail.
- Managed I/O Completion Ports (IOCP) - Part 2: This articles explains the ThreadPool and Task Framework built on top of ManagedIOCP, in detail.
Reading the above three articles enables the readers of ACB to get a more in-depth understanding of the workings of ACB, and will enable them to use ACB more effectively and efficiently. The Sonic.Net library is not required to use ACB. I converted ManagedIOCP to .NET 2.0, and customized the source for use with ACB directly. The version of ManagedIOCP that comes with this ACB library uses .NET 2.0 Generic Type for the objects disposed to it. Developers can use this new ManagedIOCP infrastructure including its ThreadPool
class, independent of ACB, in their .NET 2.0 applications. For those who have not read my above three articles or who have already read them, and cannot wait to use ACB, here we go.
2. Using Asynchronous Code Blocks
The ZIP file attached to this article contains the complete C# 2.0 source for ACB, including updated ManagedIOCP classes. The ZIP contains the following folders.
AsynchronousCodeBlocks
|
-- AsynchronousCodeBlocks
-- WinTestAsyncCodeBlocks
As shown in the above folder structure, the root folder 'AsynchronousCodeBlocks' contains the solution file that has two projects, one is the C# 2.0 library for AsynchronousCodeBlocks, and the second is a WinForms based sample application demonstrating the various features of the AsynchronousCodeBlocks library. The root folder also contains a SQL file named AsyncTestDB.sql that contains the SQL script to create a SQL Server 2000/2005 database, which is used in the sample application. This is all about the attached ZIP file. Let us dive into understanding and using the AsynchronousCodeBlocks library.
When we want to execute a portion of the method code asynchronously, just wrap it inside an asynchronous class as shown in the code below:
private void button5_Click_1(object sender, EventArgs e)
{
SqlConnection con = new SqlConnection("server=.;" +
"database=AsyncTestDB;uid=sa;pwd=AdiTanuL1$");
con.Open();
new async(delegate
{
try
{
int age = 28;
string cmdStr = string.Format("insert into Address values" +
" ('{0}',{1},'{2}')", "Name_" + age.ToString(),
age, "Address_" + age.ToString());
SqlCommand cmd = new SqlCommand(cmdStr, con);
cmd.ExecuteNonQuery();
}
finally
{
con.Close();
}
});
}
If you observe the above code snippet, apart from the highlighted code,s the rest of the code is normal and is used to execute an INSERT
command on the connection object synchronously. Now, the highlighted code allows us to wrap the code into an asynchronous object instance, which will be immediately scheduled for execution on the AppDomain-wide ManagedIOCP ThreadPool. The code that is wrapped inside the asynchronous object itself is an anonymous method that defines a delegate named AsyncDelegate
. The async
class is the core class that schedules the wrapped anonymous method for execution from its constructor and provides different features that we will discuss shortly. Since these anonymous methods, which are part of a method, are executed asynchronously, I named this technique and the class library containing the asynchronous and companion classes as Asynchronous Code Blocks.
The code shown above is incomplete in the sense that we do not have a clue on when this asynchronous code block will complete its execution. If we have scheduled several such asynchronous code blocks for execution, we may want to execute a method or another piece of code upon completion of execution of one or more asynchronous code blocks, or we may not want to close the application until all the pending asynchronous code blocks are executed.
ACB provides two ways of handling this situation. Firstly, ACB allows us to wait synchronously on the completion of an asynchronous code block. Secondly, ACB allows us to provide a delegate that will be called after the completion of execution of an asynchronous code block.
We can wait on asynchronous code blocks that are wrapped in a class named waitableasync
in the ACB library. The waitableasync
class is derived from the async
class, and allows for waiting synchronously on its objects. The wait on the waitableasync
object will come out when the asynchronous code block wrapped by it has completed execution, or the timeout specified on the wait method has completed. A timeout value of -1 indicates infinite timeout. The code snippet below shows the usage of the waitableasync
class.
private void button5_Click_1(object sender, EventArgs e)
{
SqlConnection con = new SqlConnection("server=.;" +
"database=AsyncTestDB;uid=sa;pwd=AdiTanuL1$");
con.Open();
waitableasync obj = new waitableasync(delegate
{
try
{
int age = 28;
string cmdStr = string.Format("insert into Address values" +
" ('{0}',{1},'{2}')", "Name_" +
age.ToString(), age, "Address_" + age.ToString());
SqlCommand cmd = new SqlCommand(cmdStr, con);
cmd.ExecuteNonQuery();
}
finally
{
con.Close();
}
});
if (obj.Wait(-1) == true)
{
MessageBox.Show("Successfully completed the insert");
}
else
{
MessageBox.Show("Failed to wait");
}
}
The async
and waitableasync
classes, by default, use the AppDomain-wide ManagedIOCP ThreadPool for executing the anonymous methods wrapped inside their objects. This ManagedIOCP ThreadPool is instantiated once per AppDomain, and stays for the lifetime of the AppDomain. It uses one thread for execution of all asynchronous code blocks (anonymous methods) posted to it by the async
and waitableasync
objects. One thread is decided because if a method has a sequence of asynchronous code blocks, and then if we execute them on multiple threads, it may lead to unpredictable results. It also won't look like natural sequential execution of code inside the method where these asynchronous code blocks are defined. But one thread for execution of all asynchronous code blocks is also limiting because the asynchronous code execution in one method has to wait until any pending asynchronous code block executions in other unrelated methods are completed. But the default AppDomain-wide ManagedIOCP ThreadPool approach makes the ACB code concise, and look naturally integrated into the containing method code. To handle the restriction on the threads in the default AppDomain-wide ManagedIOCP ThreadPool, we can pass our own instance of the ManagedIOCP ThreadPool while constructing/instantiating async
and waitableasync
objects. The asynchronous code block associated with the async
and waitableasync
objects with the user-supplied ManagedIOCP ThreadPool instance will be executed on the threads related to the user defined ThreadPool. This provides developers with fine grained control on when and how the asynchronous code blocks are executed. The code snippet below shows the usage of a custom ManagedIOCP ThreadPool with a waitableasync
class.
private void button6_Click(object sender, EventArgs e)
{
int maxPrimeNum = Convert.ToInt32(textBox2.Text);
int iFirstHalfStart = 1;
int iFirstHalfEnd = maxPrimeNum / 2;
int iSecondHalfStart = iFirstHalfEnd + 1;
int iSecondHalfEnd = maxPrimeNum;
int countPrime = 0;
long st = DateTime.Now.Ticks;
waitableasync firstHalf = new waitableasync(delegate
{
for (int i = iFirstHalfStart; i <= iFirstHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
},_myThreadPool);
waitableasync secondHalf = new waitableasync(delegate
{
for (int i = iSecondHalfStart; i <= iSecondHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
},_myThreadPool);
firstHalf.Wait(-1);
secondHalf.Wait(-1);
long et = DateTime.Now.Ticks;
MessageBox.Show(string.Format("Done: {0}, Time: {1}",
countPrime,(et-st)/10000));
}
private Sonic.Net.ThreadPool _myThreadPool =
new Sonic.Net.ThreadPool(5, 2);
If you see the above code, we are using a custom ManagedIOCP ThreadPool
object that has maximum of five threads and two concurrent threads. This allows the above code to run on multiple threads. Using multiple threads is ideal in the sample code shown above where the sequence in which prime numbers are identified is not important. By using a multithreaded ManagedIOCP ThreadPool
, the performance of the application will improve significantly. For instance, the above code takes an average of 2100 milliseconds to complete on my laptop with a configuration Intel Centrino Duo 1.66 GHz and 500 MB RAM. If I comment the waitableasync
object creations and run the above code as regular synchronous operations, it takes an average of 3900 milliseconds. The difference is 100% performance gain with the asynchronous approach.
As shown in the above code sample, we can wait on the completion of waitableasync
objects independently, or create an array and wait on the array of waitableasync
objects. The waitableasync
class uses an Event
object for achieving a wait on the execution completion of the asynchronous code blocks wrapped by its instance. So, if we want to wait on multiple waitableasync
objects using WaitHandle.WaitAll
, there is a limitation of a maximum of 64 handles to wait for. For this reason, waitableasync
provides two different methods to wait on an array of waitableasync
objects. One method, WaitAll
, uses the WaitHandle.WaitAll
method for waiting on an array of waitableasync
objects. This method has a limitation of being able to wait on a maximum of 64 waitableasync
objects, which is a basic limitation of the WaitHandle
on the event object internally used by each waitableasync
object. Also, WaitHandle.WaitAll
cannot be used in a STA thread, which is the typical threading model in which WinForms run. The second method WaitAllEx
uses a simple algorithm to wait on an array of waitableasync
objects. This method has no limitation on the number of waitableasync
objects it can wait on or the type of threading model the calling thread is running. The code below shows the usage of waiting on an array of waitableasync
objects.
private void button6_Click(object sender, EventArgs e)
{
int maxPrimeNum = Convert.ToInt32(textBox2.Text);
int iFirstHalfStart = 1;
int iFirstHalfEnd = maxPrimeNum / 2;
int iSecondHalfStart = iFirstHalfEnd + 1;
int iSecondHalfEnd = maxPrimeNum;
int countPrime = 0;
long st = DateTime.Now.Ticks;
waitableasync firstHalf = new waitableasync(delegate
{
for (int i = iFirstHalfStart; i <= iFirstHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
},_myThreadPool);
waitableasync secondHalf = new waitableasync(delegate
{
for (int i = iSecondHalfStart; i <= iSecondHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
},_myThreadPool);
List<waitableasync> waitableasyncList =
new List<waitableasync>();
waitableasyncList.Add(firstHalf);
waitableasyncList.Add(secondHalf);
waitableasync.WaitAllEx(waitableasyncList.ToArray(), -1);
long et = DateTime.Now.Ticks;
MessageBox.Show(string.Format("Done: {0}, Time: {1}",
countPrime,(et-st)/10000));
}
We can also wait on the completion of any one of the waitableasync
objects in an array of waitableasync
objects. This can be done using the WaitAny
static method of the waitableasync
class. The code snippet below shows the usage of the WaitAny
method:
waitableasync.WaitAny(waitableasyncList.ToArray(), -1);
Wait methods are synchronous, and blocks the thread from which the wait methods are called. These methods are useful if the application is done with its other tasks and just want to wait on the completion of asynchronous code blocks. There may be situations where we want to execute a piece of code or continue with our application flow after completion of an asynchronous code block, but we do not want to explicitly and synchronously wait for the completion of the asynchronous code block. In such situations, we can supply a delegate object to the constructor of the async
or waitableasync
objects, which will be called after completion of execution of the respective async
or waitableasync
objects. The signature of this execution complete delegate is shown below:
public delegate void AsyncCodeBlockExecutionCompleteCallback(async objAsync);
As seen in the above signature, this delegate is passed the async
or waitableasync
(derived from the async
class) object to which this delegate is supplied. This way, when the method attached to this delegate is executed, it will have access to the owning async
or waitableasync
object that had just completed its execution. The code below shows the usage of the AsyncCodeBlockExecutionCompleteCallback
delegate:
private void button6_Click(object sender, EventArgs e)
{
int maxPrimeNum = Convert.ToInt32(textBox2.Text);
int iFirstHalfStart = 1;
int iFirstHalfEnd = maxPrimeNum / 2;
int iSecondHalfStart = iFirstHalfEnd + 1;
int iSecondHalfEnd = maxPrimeNum;
int countPrime = 0;
long st = DateTime.Now.Ticks;
waitableasync firstHalf = new waitableasync(delegate
{
for (int i = iFirstHalfStart; i <= iFirstHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
}, _myThreadPool, delegate(async objAsync)
{
for (int i = iSecondHalfStart; i <= iSecondHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
long et = DateTime.Now.Ticks;
MessageBox.Show(string.Format("Done: {0}, Time: {1}",
countPrime, (et - st) / 10000));
});
}
As shown in the above code, I supplied a delegate while creating the object of the waitableasync
class. This delegate will be executed upon completion of the execution of the asynchronous code block wrapped by the waitableasync
object. If you see the above code, I used an anonymous method to define the delegate. This allowed me to use the local variables from the outer scope easily and seamlessly. Without the anonymous method, if I had defined my own method with the delegate signature, then it would have been extremely difficult to pass the local variables like iSecondHalfStart
, iSecondHalfEnd
, and st
to the delegate method. This is the power and usefulness of anonymous methods.
The delegate that is supplied to the async
or waitableasync
object is executed synchronously on the ManagedIOCP ThreadPool thread that had executed the code wrapped by the corresponding async
or waitableasync
object. We can achieve further asynchrony by wrapping the code inside this delegate into an async
or waitableasync
object. This simple technique helps us execute linked code blocks or a flow of dependent code blocks asynchronously. The code below shows this technique:
private void button6_Click(object sender, EventArgs e)
{
int maxPrimeNum = Convert.ToInt32(textBox2.Text);
int iFirstHalfStart = 1;
int iFirstHalfEnd = maxPrimeNum / 2;
int iSecondHalfStart = iFirstHalfEnd + 1;
int iSecondHalfEnd = maxPrimeNum;
int countPrime = 0;
long st = DateTime.Now.Ticks;
waitableasync firstHalf = null;
waitableasync secondHalf = null;
firstHalf = new waitableasync(delegate
{
for (int i = iFirstHalfStart; i <= iFirstHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
}, _myThreadPool, delegate(async objAsync)
{
secondHalf = new waitableasync(delegate
{
for (int i = iSecondHalfStart; i <= iSecondHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
long et = DateTime.Now.Ticks;
MessageBox.Show(string.Format("Done: {0}, Time: {1}",
countPrime, (et - st) / 10000));
}, _myThreadPool);
});
}
In the above code, we used a variable secondHalf
of type waitableasync
, which wraps the execution-complete delegate code. But the waitablesasync
object that will be assigned to this variable will not be created until the waitableasync
object represented by the variable firstHalf
has completed its execution. So in the above button click handler method, we cannot use the variable secondHalf
until the asynchronous code block represented by the variable firstHalf
has completed its execution. In this case, it becomes necessary in some situations to wait for the completion of the firstHalf
object before we could wait on the completion of the secondhalf
object. This dependency may cause unwanted/undesired restrictions while designing applications using ACB. To overcome such restrictions, ACB has another way of linking and wiring dependent asynchronous code blocks.
While creating an object of the async
or waitableasync
class, we can provide another async
or waitableasync
on which the newly created object should depend. This dependency relation means that the newly created async
or waitableasync
object will be scheduled for execution only when the async
or waitableasync
object on which it depends has completed its execution. The code below shows how to program this dependency between two async
or waitableasync
objects:
private void button6_Click(object sender, EventArgs e)
{
int maxPrimeNum = Convert.ToInt32(textBox2.Text);
int iFirstHalfStart = 1;
int iFirstHalfEnd = maxPrimeNum / 2;
int iSecondHalfStart = iFirstHalfEnd + 1;
int iSecondHalfEnd = maxPrimeNum;
int countPrime = 0;
long st = DateTime.Now.Ticks;
waitableasync firstHalf = new waitableasync(delegate
{
for (int i = iFirstHalfStart; i <= iFirstHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
}, _myThreadPool);
waitableasync secondHalf = new waitableasync(delegate
{
for (int i = iSecondHalfStart; i <= iSecondHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
long et = DateTime.Now.Ticks;
MessageBox.Show(string.Format("Done: {0}, Time: {1}",
countPrime, (et - st) / 10000));
},_myThreadPool,firstHalf);
}
As shown in the above code, the waitableasync
object represented by the secondHalf
variable is dependent on the waitableasync
object represented by the firstHalf
variable. This way, we have the benefit of linking the execution of the secondHalf
waitableasync
object to the completion of execution of the firstHalf
waitableasync
object, and the secondHalf
object is immediately available for doing wait operations, which is not the case if the secondHalf
object is created within the execution completion delegate of the firstHalf
waitableasync
object, as seen earlier.
We can also make an async
or waitableasync
object dependent on an array of async
or waitableasync
objects. The dependee async
or waitableasync
object will be scheduled for execution when all the async
or waitableasync
objects on which it is dependent have completed their execution. This feature is not possible with the execution completion delegate that we discussed earlier. The code below shows the usage of dependency on multiple async
or waitableasync
objects:
private void button6_Click(object sender, EventArgs e)
{
int maxPrimeNum = Convert.ToInt32(textBox2.Text);
int iFirstHalfStart = 1;
int iFirstHalfEnd = maxPrimeNum / 2;
int iSecondHalfStart = iFirstHalfEnd + 1;
int iSecondHalfEnd = maxPrimeNum;
int countPrime = 0;
long st = DateTime.Now.Ticks;
waitableasync firstHalf = new waitableasync(delegate
{
for (int i = iFirstHalfStart; i <= iFirstHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
}, _myThreadPool);
waitableasync secondHalf = new waitableasync(delegate
{
for (int i = iSecondHalfStart; i <= iSecondHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
},_myThreadPool);
waitableasync secondHalf2 = new waitableasync(delegate
{
for (int i = iSecondHalfStart; i <= iSecondHalfEnd; i++)
{
bool isPrime = true;
for (int j = 2; j <= (i / 2); j++)
{
if ((i % j) == 0)
{
isPrime = false;
break;
}
}
if (isPrime == true)
Interlocked.Increment(ref countPrime);
}
long et = DateTime.Now.Ticks;
MessageBox.Show(string.Format("Done: {0}, Time: {1}",
countPrime, (et - st) / 10000));
}, _myThreadPool, new waitableasync[] { firstHalf, secondHalf });
}
3. Handling Exceptions in Asynchronous Code Blocks
For all practical reasons, the code wrapped inside async
or waitableasync
objects may throw exceptions. Since the asynchronous code blocks execute on a separate ManagedIOCP ThreadPool thread, there is no way the thread from which the asynchronous code block is scheduled for execution can know about this exception. For this reason, ACB captures any exceptions that occur while executing an asynchronous code block, and makes it available through a property named CodeExcpetion
on the async
class object that wraps this asynchronous code block. The code below shows the usage of CodeExcpetion
on the async
class object:
private void button5_Click_1(object sender, EventArgs e)
{
SqlConnection con = new SqlConnection("server=.;" +
"database=AsyncTestDB;uid=sa;pwd=AdiTanuL1$");
con.Open();
waitableasync obj = new waitableasync(delegate
{
int age = 28;
string cmdStr = string.Format("insert into Address" +
" values ('{0}',{1},'{2}')", "Name_" +
age.ToString(), age, "Address_" +
age.ToString());
SqlCommand cmd = new SqlCommand(cmdStr, con);
cmd.ExecuteNonQuery();
}, _myThreadPool, delegate(async objAsync)
{
if (con.State == ConnectionState.Open) con.Close();
if (objAsync.CodeException != null)
MessageBox.Show(string.Format("Failed. Error: {0}",
objAsync.CodeException.Message));
else
MessageBox.Show("Successfully completed the insert");
});
}
In the above code sample, we are just trying to do some insert into the 'Address' table in a database named 'AsyncTestDB'. We supplied an execution completion delegate for our asynchronous code block, where we are checking for any exceptions and displaying the appropriate message.
What if the code inside the execution completion delegate itself throws an exception? As we discussed earlier, the execution completion delegate will be executed on the same ManagedIOCP ThreadPool thread that had completed the execution of the associated asynchronous code block. So, if the execution completion delegate throws any exceptions, it will be ignored by ManagedIOCP unless you have supplied a delegate to the ManagedIOCP instance to handle exceptions. But handling asynchronous code block exceptions in ManagedIOCP is not practical, and the information about the async
or waitableasync
object where the exception occurred is lost. For this reason, ACB also captures any exception that occurred during execution of the execution completion delegate associated with an async
or waitableasync
object. This exception is made available through a property named 'ExecutionCompletionDelegateException
' on the async
class object.
4. WinForms and Asynchronous Code Blocks
Control objects like Form, TextBox
, ListBox
, Button
, ProgressBar
, etc., in WinForms have thread affinity. This means that if a WinForms Control object is created in Thread 1, its window related properties or methods should be accessed only from Thread 1, not from any other threads. Being said this, take a look at the code below:
private void button1_Click(object sender, EventArgs e)
{
progressBar1.Minimum = 1;
progressBar1.Maximum = 10000;
progressBar1.Value = 1;
new waitableasync(delegate
{
for(int i = 1; i <= 10000; i++)
{
textBox1.Text = i.ToString();
progressBar1.Value = i;
}
});
}
The issue with the above code is that the asynchronous code block executes on a ManagedIOCP ThreadPool thread, which is not the thread on which the TextBox
(textBox1
) and ProgressBar
(progressBar1
) controls are created. This code may execute properly but is not guaranteed to work reliably. This shows that accessing WinForms controls from the asynchronous code block directly is not correct. So, we somehow need to be able to execute the code that sets the properties on the WinForms controls, on the thread in which these controls are created. This can be achieved using a .NET 2.0 FCL class named SynchronizationContext
. The code below shows the usage of this class to correctly access WinForms controls from asynchronous code blocks.
private void button1_Click(object sender, EventArgs e)
{
SynchronizationContext sc = SynchronizationContext.Current;
progressBar1.Minimum = 1;
progressBar1.Maximum = 10000;
progressBar1.Value = 1;
new waitableasync(delegate
{
for(int i = 1; i <= 10000; i++)
{
sc.Send(delegate(object state)
{
textBox1.Text = i.ToString();
progressBar1.Value = i;
},null);
}
});
}
In the above code, we are retrieving the SynchronizationContext
object associated with the current thread, using the static property named Current
on the SynchronizationContext
class. This would be an object of the WindowsFormsSynchronizationContext
class, which is set as the current thread's SynchronizationContext
when our main form of this test application is created. This current SynchronizationContext
object has information about the current thread. We use the same Synchronization
object from within our asynchronous code block, by capturing it (using the magic of anonymous methods again), to execute the code that accesses the controls on the main form. The Send
method of the SynchronizationContext
object takes the supplied delegate, and executes it on the thread represented by the SynchronizationContext
object. In our case, the delegate supplied to the Send
method of the sc
object is executed on the thread on which the main forms of our test application and the controls on it like textBox1
are created. Explaining the SynchronizationContext
further is outside the scope of this article. I would request readers to look in CodeProject for dedicated articles on this new .NET 2.0 class.
5. Points of Interest
By the time I completed the Asynchronous Code Blocks library, it has become my favorite technique and .NET library. It opens up new ways of programming techniques for building asynchronous applications. One point that always fascinated me while creating ACB is the serialization of async
objects. I tried serializing the async
objects, but there looks to be some unknowns and technical hurdles at this stage. The ACB library attached to this article has commented code that shows my attempt to serialize the async
objects. One of the issues that I faced is that the wrapper classes that are generated by the C# 2.0 compiler for the anonymous methods are not marked as serializable. I will continue my efforts in this area, and see if it is possible to serialize the async
objects. If we are able to achieve this, it unlocks a great potential of ACB. We will be able to schedule async
objects for execution, serialize them, and execute them later for persisted long running business activities or on separate systems for scalability.
6. Software Usage
This software is provided "as is" with no expressed or implied warranty. I accept no liability for any type of damage or loss that this software may cause.
7. History
- Oct 02, 2006 - Asynchronous Code Blocks v1.0.0.0.