e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
115 lines
4.8 KiB
C#
115 lines
4.8 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="BackgroundWorkScheduler.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
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<BackgroundWorkScheduler> _unregisterCallback;
|
|
private readonly Action<AppDomain, Exception> _logCallback;
|
|
private readonly Action _workItemCompleteCallback;
|
|
|
|
internal BackgroundWorkScheduler(Action<BackgroundWorkScheduler> unregisterCallback, Action<AppDomain, Exception> 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<CancellationToken, Task> 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<CancellationToken, Task> 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<CancellationToken, Task>)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();
|
|
}
|
|
}
|
|
}
|
|
}
|