I am using asynchronous methods in some of my project and I like it since it allows my application to be a lot more scalable. However, I am wondering how asynchronous methods really work in background? How .NET (or Windows?) knows that a call is completed? Depending on the number of asynchronous calls I made, I can see that new threads are created (not always though…). Why?
In addition, I would like to monitor how long a request take to complete. To test the concept, I wrote the following code which calls asynchronously a Web service and immediately after starts a stopwatch.
for (int i = 0; i < 10000; i++)
{
myWebService.BeginMyMethod(new MyRequest(), result, new AsyncCallback(callback), i);
stopWatches[i].Start();
}
// Call back stop the stopwatch after calling EndMyMethod
This doesn’t work since all the requests (10000) have the same begin time and the duration will go up linearly (call 0 = duration 1, call 1 = duration 2, etc.). How could I monitor the real duration of a call with asynchronous method (from the moment the request is really executed until the end)?
UPDATE: Does an asynchronous method block the flow? I understand that it uses the .NET ThreadPool
but how an IAsyncResult
know that a call is completed and it's time to call the CallBack
method?
Its possible that a majority of the execution time happens before
BeginMyMethod()
. In that case your measurement will be too low. In fact, depending on the API,BeginMyMethod()
may call the callback before leaving the stack itself. Moving up the call toStopWatch.Start()
should help then.Asynchronous methods work by using the .NET
ThreadPool
. They will push the work onto aThreadPool
thread (potentially creating one if needed, but usually just reusing one) in order to work in the background.In your case, you can do what you're doing, however, realize that the
ThreadPool
has a limited number of threads with which it will work. You're going to spawn your work onto background threads, and the first will run immediately, but after a while, they will queue up, and not work until "tasks" run before completely. This will give the appearance of the threads taking longer and longer.However, your stopwatch criteria is somewhat flawed. You should measure the total time it takes to complete N tasks, not N times to complete one task. This will be a much more useful metric.
The crux of it is that calling
Begin
queues up a request to execute your method. The method is actually executed on the ThreadPool, which is a set of worker threads provided by the runtime.The threadpool is a fixed set of threads to crunch through async tasks as they get put into the queue. That explains why you see the execution time take longer and longer - your methods may each execute in approximately the same time, but they don't start until all previous methods in the queue have been executed.
To monitor the length of time it takes to actually execute the async method, you have to start and stop the timer at the beginning and end of your method.
Here's the docs for the ThreadPool class, and an article about async methods that do a better job of explaining what's going on.
The code is the railroad and the thread is the train. As train goes on railroad it executes the code.
BeginMyMethod
is executed by the main thread. If you look inside theBeginMyMethod
it simply adds a delegate ofMyMethod
to theThreadPool
's queue. The actualMyMethod
is executed by one of the trains of the train pool. The completion routine that is called whenMyMethod
is done is executed by the same thread that executed theMyMethod
, not by your main thread that runs the rest of the code. While a thread pool thread is busy executingMyMethod
, the main thread can either ride some other portion of the railroad system (execute some other code), or simply sleep, waiting until certain semaphore is lit up.Therefore there's no such thing as
IAsyncResult
"knowing" when to call the completion routine, instead, completion routine is simply a delegate called by the thread pool's thread right after it's done executingMyMethod
.I hope you don't mind the somewhat childish train analogy, I know it helped me more than once when explaining multithreading to people.