views:

743

answers:

4

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?

+1  A: 

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.

Nader Shirazie
+1  A: 

Asynchronous methods work by using the .NET ThreadPool. They will push the work onto a ThreadPool 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.

Reed Copsey
+2  A: 

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 the BeginMyMethod it simply adds a delegate of MyMethod to the ThreadPool's queue. The actual MyMethod is executed by one of the trains of the train pool. The completion routine that is called when MyMethod is done is executed by the same thread that executed the MyMethod, not by your main thread that runs the rest of the code. While a thread pool thread is busy executing MyMethod, 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 executing MyMethod.

I hope you don't mind the somewhat childish train analogy, I know it helped me more than once when explaining multithreading to people.

zvolkov
I like the train analogy! Thanks! :)
Martin
A: 

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 to StopWatch.Start() should help then.

Frank Schwieterman