IAsyncResult vs ThreadPool
Asked Answered
R

3

13

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:

  • Added complexity with the BeginXXX and EndXXX
  • Caller may forget calling EndXXX if he doesn't care about the return value
  • Increased redundancies in API design (we need to create Begin and End wrapper methods for every methods we want to run asynchronously)
  • Reduced readability

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" );
  }
Roughrider answered 22/1, 2014 at 9:14 Comment(3)
Oh wow... if you are this excited about what you've put here.. wait until you get to Tasks and async/await.Plaudit
There is no difference here because you still use ThreadPool in your second example to download synchronously (blocking a thread for the duration). The advantages become apparent when you use asynchronous calls throughout, WebClient.DownloadStringAsync, HttpWebRequest.BeginGetResponse, Stream.BeginRead and so on.Bullivant
The huge difference is that with async, the waiting for data doesn't block a threadpool thread, only when the data is received or sent, it will invoke the endreceive on the threadpool.Kameko
C
17

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.

Cattery answered 26/1, 2014 at 15:3 Comment(6)
In the example above the internal implementation for BeginXXX uses TP (and not DownloadXXXAsync from the BCL) as well so I assume it would still occupy the TP for however long it takes to perform the network request. I'm writing the API myself and I do not have the access to marshal the request to hardware / OS level and utilize the advantage you have stated above. In that case what would be a better implementation to go for? Because I'm stuck between using TP explicitly or BeginXXXX for my API. Would it be better if I pick BeginXXXX then use a normal thread to perform the request?Roughrider
It just isn't that clear to me why you are not using WebClient.DownloadStringAsync().Cattery
Apologies for that. Maybe I've picked a wrong example to illustrate my problem. The implementation could be a mathematical computation or a database call (or anything that blocks user for a period of time).Roughrider
@Roughrider if it is a mathmatical computation it should block and be syncronous, if the user wants it to not block let them put the long action on a thread. If it is a database call you should expose both a synchronous and a asynchronous implementation if your database provider supports both, if your provider only has sync calls then you should only expose sync calls in your API.Mundane
Why is a .Net managed thread considered expensive? If the explicit use of threads allows clearer code, perhaps it is preferable to block the thread and rely on the threadpool to make a new thread available if subsequent work is in the queue. It seems to me that the purpose of a thread pool, like a db connection pool, is to incur the cost of the thread at a convenient time so it can be ready for use.Frazil
@Hans Passant Just to add an additional thought to my comment above, it seems to me the only cost of a waiting thread is its memory footprint. Does that cost justify the extra code complexity?Frazil
T
6

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.

Taste answered 29/1, 2014 at 0:24 Comment(0)
I
0

Basically these two ways are just different behavior of the same thing. One of the reason to use IAsyncResult over ThreadPool is return value: Threading.WaitCallback returns void so you can't return any value directly by ThreadPool.QueueUserWorkItem calling but you can in IAsyncResult approach.

Incommodious answered 26/1, 2014 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.