Using QueuedTaskScheduler from the ParallelExtensionsExtras I face the following issue: When the AppDomain that the scheduler threads are running in is unloaded (in my case due to deploying a new code version to an ASP.NET site) the threads go into an infinite spinning loop. The relevant code is this:
// If a thread abort occurs, we'll try to reset it and continue running.
while (true)
{
try
{
// For each task queued to the scheduler, try to execute it.
foreach (var task in _blockingTaskQueue.GetConsumingEnumerable(_disposeCancellation.Token))
{
//[...] run task
}
}
catch (ThreadAbortException)
{
// If we received a thread abort, and that thread abort was due to shutting down
// or unloading, let it pass through. Otherwise, reset the abort so we can
// continue processing work items.
if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload())
{
Thread.ResetAbort();
}
}
}
The scheduler tries to detect the case that its AppDomain is being unloaded but unfortunately the condition as written turns out to be false at runtime. This causes the abort to be reset and the loop to continue.
I understand that a pending abort is not raised immediately. Only sometimes the jitted code polls for a pending abort and raises the TAE. According to the call stacks that I observe this seems to be inside GetConsumingEnumerable here.
So the threads never exit the loop and continue spinning. (Even if this interpretation is wrong, the threads provably do end up in GetConsumingEnumerable and consume lots of CPU there).
What is an appropriate fix for this code? It seems not to be possible to detect that the current AppDomain is being unloaded (AppDomain.CurrentDomain.IsFinalizingForUnload is probably false because we are not finalizing). I considered to modify the code to just never reset the abort (which fixes the issue). But what would have been the appropriate fix?
(I'm less interested interested in workarounds because I already have one.)
Have you tried something like this (untested)?
Updated, I'm not sure about ASP.NET, but the following produces the correct sequence of events in a console app. I don't see
"End of DoCallBack"even though I callThread.ResetAbort(). I guess it makes sense because the code insideDoCallBackis supposed to be executed only on the domain which no longer exists:Output: