//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.Hosting { using System; using System.Threading; using System.Threading.Tasks; using System.Web.Util; internal sealed class BackgroundWorkScheduler : IRegisteredObject { private readonly CancellationTokenHelper _cancellationTokenHelper = new CancellationTokenHelper(canceled: false); private int _numExecutingWorkItems; // number of executing work items, not scheduled work items; a work item might never be scheduled private readonly Action _unregisterCallback; private readonly Action _logCallback; private readonly Action _workItemCompleteCallback; internal BackgroundWorkScheduler(Action unregisterCallback, Action logCallback, Action workItemCompleteCallback = null) { Debug.Assert(unregisterCallback != null); _unregisterCallback = unregisterCallback; _logCallback = logCallback; _workItemCompleteCallback = workItemCompleteCallback; } private void FinalShutdown() { _unregisterCallback(this); } // we can use 'async void' here since we're guaranteed to be off the AspNetSynchronizationContext private async void RunWorkItemImpl(Func workItem) { Task returnedTask = null; try { returnedTask = workItem(_cancellationTokenHelper.Token); await returnedTask.ConfigureAwait(continueOnCapturedContext: false); } catch (Exception ex) { // ---- exceptions caused by the returned task being canceled if (returnedTask != null && returnedTask.IsCanceled) { return; } // ---- exceptions caused by CancellationToken.ThrowIfCancellationRequested() OperationCanceledException operationCanceledException = ex as OperationCanceledException; if (operationCanceledException != null && operationCanceledException.CancellationToken == _cancellationTokenHelper.Token) { return; } _logCallback(AppDomain.CurrentDomain, ex); // method shouldn't throw } finally { WorkItemComplete(); } } public void ScheduleWorkItem(Func workItem) { Debug.Assert(workItem != null); if (_cancellationTokenHelper.IsCancellationRequested) { return; // we're not going to run this work item } // Unsafe* since we want to get rid of Principal and other constructs specific to the current ExecutionContext ThreadPool.UnsafeQueueUserWorkItem(state => { lock (this) { if (_cancellationTokenHelper.IsCancellationRequested) { return; // we're not going to run this work item } else { _numExecutingWorkItems++; } } RunWorkItemImpl((Func)state); }, workItem); } public void Stop(bool immediate) { // Hold the lock for as little time as possible int currentWorkItemCount; lock (this) { _cancellationTokenHelper.Cancel(); // dispatched onto a new thread currentWorkItemCount = _numExecutingWorkItems; } if (currentWorkItemCount == 0) { // There was no scheduled work, so we're done FinalShutdown(); } } private void WorkItemComplete() { // Hold the lock for as little time as possible int newWorkItemCount; bool isCancellationRequested; lock (this) { newWorkItemCount = --_numExecutingWorkItems; isCancellationRequested = _cancellationTokenHelper.IsCancellationRequested; } // for debugging & unit tests if (_workItemCompleteCallback != null) { _workItemCompleteCallback(); } if (newWorkItemCount == 0 && isCancellationRequested) { // Cancellation was requested, and we were the last work item to complete, so everybody is finished FinalShutdown(); } } } }