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.
Task
s andasync/await
. – Plaudit