Threadpool deadlock with Task.Result
You have explained your issue very well.
As you are using Task.Run
then blocking on the result, you are using 1-2 threads, when really with async you want to use 0-1 threads.
If you are using Task.Run
too liberally through your code then there is a chance you'll have multiple layers of blocking threads, making thread usage get really ugly fast, and you'll hit the maximum capacity as you've described.
As an aside, forget trying to find async deadlocks in unit tests (or console apps) as it requires as non-default SynchronizationContext
.
The best and right solution would be to make everything top-to-bottom async or sync, but given you are constrained, I'd suggest investigating this wonderful library from the Microsoft vs team and look at JoinableTaskFactory.Run(...)
, this will run continuations on the blocking thread, and plays well when you nest this pattern at multiple levels. Using this approach, you will get closer to the synchronous equivalent code.
To reiterate, these techniques are workarounds, and if you are justifying these workarounds by respecting the existing code, the best way to respect it is to do it right, and make it fully sync, or top-to-bottom async.
You can safely use short form (Foo().Result
) instead of Task.Run(() => Foo()).Result
if you disable aspnet:UseTaskFriendlySynchronizationContext
setting:
<appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext" value="false" /></appSettings>
Disabling task-friendly synchronization context means that HttpContext.Current would be null after any await operator - but now it is null inside of Task.Run.
Using Foo().Result
instead of Task.Run(() => Foo()).Result
will lead to 2 times less thread pool usage, so it can solve your problem.
Also you can use <httpRuntime>
and <processModel>
to configure minimum free thread pool size:
<system.web> <processModel autoConfig="false" maxWorkerThreads="..." maxIoThreads="..." /> <httpRuntime minFreeThreads="..." /></system.web>
Note that default values are:
maxWorkerThreads = 100 per cpu
maxIoThreads = 100 per cpu
minFreeThreads = 88 per cpu