I've just come across IAsyncResult recently and have played with it for quite some time. What I'm actually wondering is why use IAsyncResult when we have a way better alternative ThreadPool there? From my current understanding about both of them, I would choose to use ThreadPool in almost every situation. So my question is, is there any context where IAsyncResult is preferred over another?
Why I do not prefer IAsyncResult:
To put it in code:
ThreadPool
public void ThreadPoolApproach()
{
ThreadPool.QueueUserWorkItem( ( a ) =>
{
WebClient wc = new WebClient();
var response = wc.DownloadString( "http://www.test.com" );
Console.WriteLine( response );
} );
}
IAsyncResult
public void IAsyncResultApproach()
{
var a = BeginReadFromWeb( ( result ) =>
{
var response = EndReadFromWeb( result );
Console.WriteLine( response );
}, "http://www.test.com" );
}
public IAsyncResult BeginReadFromWeb( AsyncCallback a, string url )
{
var result = new AsyncResult<string>( a, null, this, "ReadFromFile" );
ThreadPool.QueueUserWorkItem( ( b ) =>
{
WebClient wc = new WebClient();
result.SetResult( wc.DownloadString( url ) );
result.Complete( null );
} );
return result;
}
public string EndReadFromWeb( IAsyncResult result )
{
return AsyncResult<string>.End( result, this, "ReadFromFile" );
}
No, there's a honking huge difference between your two code snippets. Both do in fact use the threadpool, the first one does it explicitly of course. The second one does it in far less visible (and broken) way, the IAsyncResult callback executes on a threadpool thread.
The threadpool is a shared resource, in a large program you'll have many uses for TP threads. Not just explicitly in your own code, the .NET Framework uses them as well. The guidance for the kind of code that runs on a threadpool is for it to be code that executes quickly and doesn't make any blocking calls that puts the TP thread into a wait state. Blocking is using a very expensive operating resource in a very inefficient way and gums up other code that might be using a TP thread. An important part of the threadpool is the scheduler, it tries to limit the number of executing TP threads to the number of CPU cores that the machine has available.
But blocking is exactly what you are doing in the first snippet. WebClient.DownloadString() is a very slow method that cannot complete any faster than your Internet connection or the server on the other end of the wire will allow. In effect, you are occupying a TP thread for, potentially, minutes. Not doing much of any work at all, it is constantly waiting for a Socket.Read() call to complete. Effective CPU core utilization is a few percent, at best.
That's much different when you use a BeginXxxx() or XxxxAsync() method. It is internally implemented as a bit of code to ask the operating system to start an I/O operation. Takes but a handful of microseconds. The OS passes the request on to a device driver, the TCP/IP stack in the case of DownloadStringAsync(). Where it will sit as a data item in an I/O request queue. Your call very quickly returns.
Eventually, your network card gets data from the server and the driver completes the I/O request. Through several layers, that gets the CLR to grab another TP thread and run your callback. You quickly do whatever you do with the data, some kind of processing step that normally takes microseconds as well.
Note the difference, your first code is occupying a TP thread for minutes, the async version ties up threads for microseconds. The async version scales much better, being capable of handling many I/O requests.
A significant problem with the asynchronous version of the code is that it is much harder to write correctly. What will be local variables in the synchronous version need to become fields of a class in the asynchronous version. It is also much harder to debug. Which is why .NET got the Task class, further extended later with the support for async/await keywords in the C# and VB.NET languages.
Let's set aside the naturally asynchronous IO-bound operations, which do not require a dedicate thread to complete (see There Is No Thread by Stephen Cleary). It doesn't make much sense to execute the synchronous version DownloadString
of the naturally asynchronous DownloadStringAsync
API on a pool thread, because your blocking a precious resource in vain: the thread.
Instead, let's concentrate on CPU-bound, computational operations, which do require a dedicated thread.
To begin with, there is no standard AsyncResult<T>
class in the .NET Framework. I believe, the implementation of AsyncResult<string>
you're referencing in your code was taking from the Concurrent Affairs: Implementing the CLR Asynchronous Programming Model article by Jeffrey Richter. I also believe the author shows how to implement AsyncResult<T>
for educational purposes, illustrating how the CLR implementation might look like. He executes a piece of work on a pool thread via ThreadPool.QueueUserWorkItem
and implements IAsyncResult
to provide the completion notification. More details can be found in LongTask.cs, accompanying the article.
So, to answer the question:
What I'm actually wondering is why use IAsyncResult when we have a way better alternative ThreadPool there?
This is not a "IAsyncResult
vs ThreadPool
" case. Rather, in the context of your question, IAsyncResult
is complementary to ThreadPool.QueueUserWorkItem
, it provides a way to notify the caller that the work item has completed executing. The ThreadPool.QueueUserWorkItem
API by itself doesn't have this feature, it simply returns bool
indicating whether the work item has been successfully queued for asynchronous execution on a pool thread.
However, for this scenario, you don't have to implement AsyncResult<T>
or use ThreadPool.QueueUserWorkItem
at all. The Framework allows to execute delegates asynchronously on ThreadPool
and track the completion status, simply by using delegate's BeginInvoke method. That's how the Framework implements the Asynchronous Programming Model (APM) pattern for delegates. For example, here's how you can perform some CPU-bound work using BeginInvoke
:
static void Main(string[] args)
{
Console.WriteLine("Enter Main, thread #" + Thread.CurrentThread.ManagedThreadId);
// delegate to be executed on a pool thread
Func<int> doWork = () =>
{
Console.WriteLine("Enter doWork, thread #" + Thread.CurrentThread.ManagedThreadId);
// simulate CPU-bound work
Thread.Sleep(2000);
Console.WriteLine("Exit doWork");
return 42;
};
// delegate to be called when doWork finished
AsyncCallback onWorkDone = (ar) =>
{
Console.WriteLine("enter onWorkDone, thread #" + Thread.CurrentThread.ManagedThreadId);
};
// execute doWork asynchronously on a pool thread
IAsyncResult asyncResult = doWork.BeginInvoke(onWorkDone, null);
// optional: blocking wait for asyncResult.AsyncWaitHandle
Console.WriteLine("Before AsyncWaitHandle.WaitOne, thread #" + Thread.CurrentThread.ManagedThreadId);
asyncResult.AsyncWaitHandle.WaitOne();
// get the result of doWork
var result = doWork.EndInvoke(asyncResult);
Console.WriteLine("Result: " + result.ToString());
// onWorkDone AsyncCallback will be called here on a pool thread, asynchronously
Console.WriteLine("Press Enter to exit");
Console.ReadLine();
}
Finally, it's worth mentioning that the APM pattern is being superseded by a much more convenient and well-structured Task-based Asynchronous Pattern (TAP). It is recommended that TAP pattern should be favored over other, more low-level APIs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With