|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Index
1. IntroductionAsynchronous 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:
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”.
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 2. Using Asynchronous Code BlocksThe 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 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 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 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 As shown in the above code sample, we can wait on the completion of 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);
// Wait on individual waitableasync objects
//
//firstHalf.Wait(-1);
//secondHalf.Wait(-1);
// Wait on array of waitablesasync objects
//
List<waitableasync> waitableasyncList =
new List<waitableasync>();
waitableasyncList.Add(firstHalf);
waitableasyncList.Add(secondHalf);
// WaitAllEx method
//
waitableasync.WaitAllEx(waitableasyncList.ToArray(), -1);
// WaitAll method
//
//waitableasync.WaitAll(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 // 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 public delegate void AsyncCodeBlockExecutionCompleteCallback(async objAsync);
As seen in the above signature, this delegate is passed the 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 The delegate that is supplied to the 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 While creating an object of the 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 We can also make an 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 BlocksFor all practical reasons, the code wrapped inside 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 4. WinForms and Asynchronous Code BlocksControl objects like Form, 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 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 5. Points of InterestBy 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 6. Software UsageThis 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
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||