8

Is it possible to force the continuation of an async-await statement to run on a thread of a custom ThreadPool?

Context: I'm running an ASP-Application and doing quite a bit of work in the background. I'm doing all the work via a self written ThreadPool, but if I use the async-await Pattern, the continuation always runs on a thread named "Worker Thread". I'm pretty sure that's a thread from the default ThreadPool which is also used to process HTTP requests. This leads to a starvation of these requests as all the threads of the default ThreadPool are busy continuing my background work.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
Martin Walter
  • 540
  • 5
  • 11
  • 1
    Instead of reinventing the weel why don't you raise the ThreadPool's limits? http://msdn.microsoft.com/en-us/library/system.threading.threadpool.setmaxthreads%28v=vs.110%29.aspx – Gusman May 30 '14 at 16:37
  • 2
    That does not sound right. You are hitting thread-pool limits? Then you probably have far too much concurrent blocking work.; Consider, just increasing the thread-pool limits. – usr May 30 '14 at 17:14
  • Increasing the thread pool limits doesn't solve my problem. I'm doing a lot of IO using completion ports so the blocking is not on the thread level. But since the project is doing massive parallel work (6000 parallel tasks or more) I want to run them completely separate from the framework thread pool. – Martin Walter May 30 '14 at 20:51
  • 1
    What do you expect to happen when a web server needs to handle more clients than it can possibly handle? It happened. – Hans Passant May 30 '14 at 21:08
  • @Hans We're talking about one single client. The problem is that all the threads the system uses to handle requests are busy waiting on my long running tasks. I want to move that to my own threads. – Martin Walter May 30 '14 at 21:18

2 Answers2

6

Yes. You can create a custom SynchronizationContext that works with your custom ThreadPool and set it before running the async operation.

var prevCtx = SynchronizationContext.Current; 
try 
{
    SynchronizationContext.SetSynchronizationContext(new ThreadPoolSynchronizationContext()); 
    
    // async operations.
} 
finally 
{  
    SynchronizationContext.SetSynchronizationContext(prevCtx); 
} 

More on how to create a custom SynchronizationContext: Await, SynchronizationContext, and Console Apps


Although a custom ThreadPool is rarely necessary. You should probably try and optimize while using the built-in one instead.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 1
    @MartinWalter, I'd warn you against installing a custom synchronization context in ASP.NET, even for the scope of single `await`. Not only you'll loose the `HttpContext.Current` and other ambient HTTP request properties, but you *may* also confuse some ASP.NET runtime APIs which rely upon `AspNetSynchronizationContext`. – noseratio May 31 '14 at 11:43
  • 1
    @Noseratio Thanks for the advice, but I don't have those problems: My work is completely independent of a HttpContext (it doesn't even know it's running inside an ASP project), and I'm changing the synchronization context only for my own threads. – Martin Walter May 31 '14 at 13:09
5

I don't think you need a custom thread pool, especially if you're doing a lot parallel I/O which naturally uses IOCP threads. This applies to any other API which only acquires a thread from the pool when its task has completed (e.g, Task.Delay).

Usually you do await task.ConfigureAwait(false) for continuation to happen on the same thread the task has completed on:

// non-IOCP (worker) continuation thread 
await Task.Delay(1000).ConfigureAwait(false); 
// IOCP continuation thread
await stream.ReadAsync(buff, 0, buff.Length).ConfigureAwait(false); 

Now, in ASP.NET you don't even need to use ConfigureAwait. ASP.NET's AspNetSynchronizationContext doesn't maintain thread affinity for await continuations, unlike WinFormsSynchronizationContext or DispatcherSynchronizationContext do for a UI app. Your continuations will run on whatever thread the task has completed on, as is. AspNetSynchronizationContext just "enters" the ASP.NET context on that thread, to make sure your code has access to HttpContext and other relevant ASP.NET request ambient state information.

You can increase the default number of IOCP and worker threads with ThreadPool.SetMinThreads to account for ThreadPool stuttering problems.

If your only reason for custom thread pool is to limit the level of parallelism for your tasks, use TPL Dataflow or simply SemaphoreSlim.WaitAsync (check "Throttling asynchronous tasks").

That said, you can have a very precise control over the Task continuation if you implement a custom awaiter. Check this question for a basic example of that.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486