UserControl
UserControl

Reputation: 15159

Understanding ASP.NET asynchronous processing

Every article on async programming in ASP.NET i've read so far states that while we make a request to a database (or other IO) the request thread is returned to ThreadPool (Page.AddOnPreRenderCompleteAsync()). As far as i understand all async IO relies on .NET feature allowing us to call any delegate asynchronously. But according to MSDN calling a delegate with BeginInvoke()/EndInvoke() still involves ThreadPool. That means if we returned the current http thread to ThreadPool (by calling to Page.AddOnPreRenderCompleteAsync()) we need another thread to execute our delegate on. What's the point?

Another issue i can't get is what happens when the async call is ended and the request processing continues. Most likely on other thread, right? (because the original thread may be reused for other requests). And that means we loose .CurrentPrincipal, .CurrentCulture and other thread context. So what's the point, again?

Update: MSDN is absolutely clear about using ThreadPool in standard BeginInvoke()/EndInvoke() pattern:

Pass a delegate for a callback method to BeginInvoke. The method is executed on a ThreadPool thread when the asynchronous call completes. The callback method calls EndInvoke.

That's correct for .NET 1.1 - .NET 4.

Upvotes: 1

Views: 2177

Answers (3)

James
James

Reputation: 1804

UPDATED: The details of what is going on is important, but I'll start with the simple answer. Your main question is how do you fix your issue with getting your thread local state information? If you have wrote your Begin and EndInvoke methods as methods in the code behind for page you have access to any of the data members on Page. So simply create a private data member IPrincipal principal_ and CultureInfo culture_ data members in your code behind and initialize them in your page_load method. Then from within your Begin and EndInvoke methods you will have direct access to them.

Now the devils in the details which hopefully answers your 'what's the point' questions: I'll talk about thread pools a little first because I don't think it is clear to you how threads relate to them. A ThreadPool may have 1 thread or N working threads working on it. When a delegate is added to a thread pool it usually doesn't actually create new threads. What happens a thread assigned to the pool which isn't doing anything already will come and do some work... if the thread finishes before its time to context switch it can take another task and run it. One thread in a thread pool is not limited to a single task. A thread might run several Tasks, which can be invoking several async handlers sequentially. The fact that a thread off the thread pool might be calling an async handler is not relevant. In this fashion many delegates can be run with a relatively few amount of threads and fewer context switches. When a threadpool is used in the async pattern most of the time the details of the jobs are querying if a blocking operation has completed, and if it had, invoking the next callback to continue or complete the work of the async operation. You may have thousands of handlers blocking and polling and only a few threads ever busy handling them all because most of the time only a few have work to be done. It's efficient and that's the point!

It is important to note that calling an Async Handler method BeginInvoke/EndInvoke does NOT mean it uses a thread or involves a ThreadPool. If IO (or whatever the job is) is fast enough there are many times when nothing is pushed on a thread pool at all. An implementation has the option to NEVER spawn a thread or push a job into a thread pool. If it doesn't do these things then the async handler will never switch context and it will not be ran in parallel with the calling thread at all. That's OK Async methods are only required to do as much work as it can and only is required to use a thread pool if it has to wait for something. The async method and callbacks might never be pushed into a separate thread. This is an important distinction which keeps situations, like buffered asynchronous IO calls, efficient without requiring any context switching.

The Book C# 4.0 in a Nutshell is an EXCELLENT resource and explains the Async pattern and methodology very well.

The clearification you make with the MSDN link is misleading because you left out its full context:

The code examples in this topic demonstrate four common ways to use BeginInvoke and EndInvoke to make asynchronous calls. After calling BeginInvoke you can do the following:

Do some work and then call EndInvoke to block until the call completes.

Obtain a WaitHandle using the System.IAsyncResult.AsyncWaitHandle property, use its WaitOne method to block execution until the WaitHandle is signaled, and then call EndInvoke.

Poll the IAsyncResult returned by BeginInvoke to determine when the asynchronous call has completed, and then call EndInvoke.

Pass a delegate for a callback method to BeginInvoke. The method is executed on a ThreadPool thread when the asynchronous call completes. The callback method calls EndInvoke.

As you can see the bullet point you quoted is relative to the example and one option, of several, so it is NOT guaranteed any method is executed on a ThreadPool. Also your link to me in the comments explains a situation for AddOnPreRenderCompleteAsync which forces the async handler on a thread pool, but again that is still situational to their example. (Article)

The important thing though is that you said you double checked that your method (which one?) is invoked on a different thread. It is probably your callback method you registered with a different Async operation? That would possibly be ran in another thread if you called BeginInvoke to a different asynchronous method from within your own handlers. Prior to making any calls like that you are most likely still in the thread associated with the Page. Even if you made a call to an async method which does implement itself by pushing something onto a thread pool at some point, if that operation was quick enough it is possible it might actually skip that step! The only reason I took this long to explain the same result of ending in a different thread, for a different reason, is so you understand the behavior of an Async call does NOT require a thread pool, but you can't rely on a context switch or not and the point of async calls is not parallelism but efficiency.

Upvotes: 1

Peter Bromberg
Peter Bromberg

Reputation: 1496

This article covers the basics and has a downloadable sample solution you can play with.

Upvotes: 2

Timur Sadykov
Timur Sadykov

Reputation: 11377

And that means we loose .CurrentPrincipal, .CurrentCulture and other thread context. So what's the point, again?

Context switching is expensive, but the concept in general is efficient while total number of threads stays within recommended threshold.

Upvotes: 0

Related Questions