1 //------------------------------------------------------------------------------
2 // <copyright file="AspNetSynchronizationContextBase.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
9 using System.Runtime.ExceptionServices;
10 using System.Threading;
11 using System.Threading.Tasks;
12 using System.Web.Util;
14 // Provides an abstraction around the different SynchronizationContext-derived types that the
15 // ASP.NET runtime might use. Consumers can target this abstraction rather than coding against
16 // the various concrete types directly.
18 internal abstract class AspNetSynchronizationContextBase : SynchronizationContext {
20 private AllowAsyncOperationsBlockDisposable _allowAsyncOperationsBlockDisposable;
22 internal abstract bool AllowAsyncDuringSyncStages { get; set; }
23 internal abstract bool Enabled { get; }
24 internal Exception Error {
26 ExceptionDispatchInfo dispatchInfo = ExceptionDispatchInfo;
27 return (dispatchInfo != null) ? dispatchInfo.SourceException : null;
30 internal abstract ExceptionDispatchInfo ExceptionDispatchInfo { get; }
31 internal abstract int PendingOperationsCount { get; }
33 internal abstract void ClearError();
34 internal abstract void Disable();
35 internal abstract void Enable();
37 internal abstract bool PendingCompletion(WaitCallback callback);
39 // A helper method which provides a Task-based wrapper around the PendingCompletion method.
40 // NOTE: The caller should verify that there are never outstanding calls to PendingCompletion
41 // or to WaitForPendingOperationsAsync, since each call replaces the continuation that will
43 internal Task WaitForPendingOperationsAsync() {
44 TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
46 WaitCallback callback = _ => {
49 // We're going to observe the exception in the returned Task. We shouldn't keep
50 // it around in the SynchronizationContext or it will fault future Tasks.
52 tcs.TrySetException(ex);
55 tcs.TrySetResult(null);
59 if (!PendingCompletion(callback)) {
60 // If PendingCompletion returns false, there are no pending operations and the
61 // callback will not be invoked, so we should just signal the TCS immediately.
68 // These methods are used in the synchronous handler execution step so that a synchronous IHttpHandler
69 // can call asynchronous methods without locking on the HttpApplication instance (possibly causing
72 internal abstract void SetSyncCaller();
73 internal abstract void ResetSyncCaller();
75 // These methods are used for synchronization, e.g. to create a lock that is tied to the current
76 // thread. The legacy implementation locks on the HttpApplication instance, for example.
78 internal abstract void AssociateWithCurrentThread();
79 internal abstract void DisassociateFromCurrentThread();
81 // These methods are used for telling the synchronization context when it is legal for an application
82 // to kick off async void methods. They are used by the "AllowAsyncDuringSyncStages" setting to
83 // determine whether kicking off an operation should throw.
85 internal virtual void AllowVoidAsyncOperations() { /* no-op by default */ }
86 internal virtual void ProhibitVoidAsyncOperations() { /* no-op by default */ }
88 // helper method for wrapping AllowVoidAsyncOperations / ProhibitVoidAsyncOperations in a using block
89 internal IDisposable AllowVoidAsyncOperationsBlock() {
90 if (_allowAsyncOperationsBlockDisposable == null) {
91 _allowAsyncOperationsBlockDisposable = new AllowAsyncOperationsBlockDisposable(this);
94 AllowVoidAsyncOperations();
95 return _allowAsyncOperationsBlockDisposable;
98 // Helper method to wrap Associate / Disassociate calls in a using() statement
99 internal IDisposable AcquireThreadLock() {
100 AssociateWithCurrentThread();
101 return new DisposableAction(DisassociateFromCurrentThread);
104 private sealed class AllowAsyncOperationsBlockDisposable : IDisposable {
105 private readonly AspNetSynchronizationContextBase _syncContext;
107 public AllowAsyncOperationsBlockDisposable(AspNetSynchronizationContextBase syncContext) {
108 _syncContext = syncContext;
111 public void Dispose() {
112 _syncContext.ProhibitVoidAsyncOperations();