1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 /*============================================================
9 ** Purpose: Capture execution context for a thread
12 ===========================================================*/
14 using System.Diagnostics;
15 using System.Diagnostics.Contracts;
16 using System.Runtime.ExceptionServices;
17 using System.Runtime.Serialization;
19 using Thread = Internal.Runtime.Augments.RuntimeThread;
21 namespace System.Threading
23 public delegate void ContextCallback(Object state);
25 internal struct ExecutionContextSwitcher
27 internal ExecutionContext m_ec;
28 internal SynchronizationContext m_sc;
30 internal void Undo(Thread currentThread)
32 Debug.Assert(currentThread == Thread.CurrentThread);
34 // The common case is that these have not changed, so avoid the cost of a write if not needed.
35 if (currentThread.SynchronizationContext != m_sc)
37 currentThread.SynchronizationContext = m_sc;
40 if (currentThread.ExecutionContext != m_ec)
42 ExecutionContext.Restore(currentThread, m_ec);
47 public sealed class ExecutionContext : IDisposable, ISerializable
49 internal static readonly ExecutionContext Default = new ExecutionContext();
51 private readonly IAsyncLocalValueMap m_localValues;
52 private readonly IAsyncLocal[] m_localChangeNotifications;
53 private readonly bool m_isFlowSuppressed;
55 private ExecutionContext()
57 m_localValues = AsyncLocalValueMap.Empty;
58 m_localChangeNotifications = Array.Empty<IAsyncLocal>();
61 private ExecutionContext(
62 IAsyncLocalValueMap localValues,
63 IAsyncLocal[] localChangeNotifications,
64 bool isFlowSuppressed)
66 m_localValues = localValues;
67 m_localChangeNotifications = localChangeNotifications;
68 m_isFlowSuppressed = isFlowSuppressed;
71 public void GetObjectData(SerializationInfo info, StreamingContext context)
73 throw new PlatformNotSupportedException();
76 public static ExecutionContext Capture()
78 ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
80 executionContext == null ? Default :
81 executionContext.m_isFlowSuppressed ? null :
85 private ExecutionContext ShallowClone(bool isFlowSuppressed)
87 Debug.Assert(isFlowSuppressed != m_isFlowSuppressed);
89 if (!isFlowSuppressed &&
90 m_localValues == Default.m_localValues &&
91 m_localChangeNotifications == Default.m_localChangeNotifications)
93 return null; // implies the default context
95 return new ExecutionContext(m_localValues, m_localChangeNotifications, isFlowSuppressed);
98 public static AsyncFlowControl SuppressFlow()
100 Thread currentThread = Thread.CurrentThread;
101 ExecutionContext executionContext = currentThread.ExecutionContext ?? Default;
102 if (executionContext.m_isFlowSuppressed)
104 throw new InvalidOperationException(SR.InvalidOperation_CannotSupressFlowMultipleTimes);
106 Contract.EndContractBlock();
108 executionContext = executionContext.ShallowClone(isFlowSuppressed: true);
109 var asyncFlowControl = new AsyncFlowControl();
110 currentThread.ExecutionContext = executionContext;
111 asyncFlowControl.Initialize(currentThread);
112 return asyncFlowControl;
115 public static void RestoreFlow()
117 Thread currentThread = Thread.CurrentThread;
118 ExecutionContext executionContext = currentThread.ExecutionContext;
119 if (executionContext == null || !executionContext.m_isFlowSuppressed)
121 throw new InvalidOperationException(SR.InvalidOperation_CannotRestoreUnsupressedFlow);
123 Contract.EndContractBlock();
125 currentThread.ExecutionContext = executionContext.ShallowClone(isFlowSuppressed: false);
128 public static bool IsFlowSuppressed()
130 ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
131 return executionContext != null && executionContext.m_isFlowSuppressed;
134 public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state)
136 if (executionContext == null)
137 throw new InvalidOperationException(SR.InvalidOperation_NullContext);
139 Thread currentThread = Thread.CurrentThread;
140 ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher);
143 EstablishCopyOnWriteScope(currentThread, ref ecsw);
144 ExecutionContext.Restore(currentThread, executionContext);
149 // Note: we have a "catch" rather than a "finally" because we want
150 // to stop the first pass of EH here. That way we can restore the previous
151 // context before any of our callers' EH filters run. That means we need to
152 // end the scope separately in the non-exceptional case below.
153 ecsw.Undo(currentThread);
156 ecsw.Undo(currentThread);
159 internal static void Restore(Thread currentThread, ExecutionContext executionContext)
161 Debug.Assert(currentThread == Thread.CurrentThread);
163 ExecutionContext previous = currentThread.ExecutionContext ?? Default;
164 currentThread.ExecutionContext = executionContext;
166 // New EC could be null if that's what ECS.Undo saved off.
167 // For the purposes of dealing with context change, treat this as the default EC
168 executionContext = executionContext ?? Default;
170 if (previous != executionContext)
172 OnContextChanged(previous, executionContext);
176 internal static void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw)
178 Debug.Assert(currentThread == Thread.CurrentThread);
180 ecsw.m_ec = currentThread.ExecutionContext;
181 ecsw.m_sc = currentThread.SynchronizationContext;
184 private static void OnContextChanged(ExecutionContext previous, ExecutionContext current)
186 Debug.Assert(previous != null);
187 Debug.Assert(current != null);
188 Debug.Assert(previous != current);
190 foreach (IAsyncLocal local in previous.m_localChangeNotifications)
192 object previousValue;
194 previous.m_localValues.TryGetValue(local, out previousValue);
195 current.m_localValues.TryGetValue(local, out currentValue);
197 if (previousValue != currentValue)
198 local.OnValueChanged(previousValue, currentValue, true);
201 if (current.m_localChangeNotifications != previous.m_localChangeNotifications)
205 foreach (IAsyncLocal local in current.m_localChangeNotifications)
207 // If the local has a value in the previous context, we already fired the event for that local
208 // in the code above.
209 object previousValue;
210 if (!previous.m_localValues.TryGetValue(local, out previousValue))
213 current.m_localValues.TryGetValue(local, out currentValue);
215 if (previousValue != currentValue)
216 local.OnValueChanged(previousValue, currentValue, true);
222 Environment.FailFast(
223 SR.ExecutionContext_ExceptionInAsyncLocalNotification,
229 internal static object GetLocalValue(IAsyncLocal local)
231 ExecutionContext current = Thread.CurrentThread.ExecutionContext;
236 current.m_localValues.TryGetValue(local, out value);
240 internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications)
242 ExecutionContext current = Thread.CurrentThread.ExecutionContext ?? ExecutionContext.Default;
244 object previousValue;
245 bool hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue);
247 if (previousValue == newValue)
250 IAsyncLocalValueMap newValues = current.m_localValues.Set(local, newValue);
253 // Either copy the change notification array, or create a new one, depending on whether we need to add a new item.
255 IAsyncLocal[] newChangeNotifications = current.m_localChangeNotifications;
256 if (needChangeNotifications)
258 if (hadPreviousValue)
260 Debug.Assert(Array.IndexOf(newChangeNotifications, local) >= 0);
264 int newNotificationIndex = newChangeNotifications.Length;
265 Array.Resize(ref newChangeNotifications, newNotificationIndex + 1);
266 newChangeNotifications[newNotificationIndex] = local;
270 Thread.CurrentThread.ExecutionContext =
271 new ExecutionContext(newValues, newChangeNotifications, current.m_isFlowSuppressed);
273 if (needChangeNotifications)
275 local.OnValueChanged(previousValue, newValue, false);
279 public ExecutionContext CreateCopy()
281 return this; // since CoreCLR's ExecutionContext is immutable, we don't need to create copies.
284 public void Dispose()
286 // For CLR compat only
290 public struct AsyncFlowControl : IDisposable
292 private Thread _thread;
294 internal void Initialize(Thread currentThread)
296 Debug.Assert(currentThread == Thread.CurrentThread);
297 _thread = currentThread;
304 throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCMultiple);
306 if (Thread.CurrentThread != _thread)
308 throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCOtherThread);
311 // An async flow control cannot be undone when a different execution context is applied. The desktop framework
312 // mutates the execution context when its state changes, and only changes the instance when an execution context
313 // is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution
314 // context from being applied by returning null from ExecutionContext.Capture, so the only type of execution
315 // context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async
316 // local's value, the desktop framework verifies that a different execution context has not been applied by
317 // checking the execution context instance against the one saved from when flow was suppressed. In .NET Core,
318 // since the execution context instance will change after changing the async local's value, it verifies that a
319 // different execution context has not been applied, by instead ensuring that the current execution context's
320 // flow is suppressed.
321 if (!ExecutionContext.IsFlowSuppressed())
323 throw new InvalidOperationException(SR.InvalidOperation_AsyncFlowCtrlCtxMismatch);
325 Contract.EndContractBlock();
328 ExecutionContext.RestoreFlow();
331 public void Dispose()
336 public override bool Equals(object obj)
338 return obj is AsyncFlowControl && Equals((AsyncFlowControl)obj);
341 public bool Equals(AsyncFlowControl obj)
343 return _thread == obj._thread;
346 public override int GetHashCode()
348 return _thread?.GetHashCode() ?? 0;
351 public static bool operator ==(AsyncFlowControl a, AsyncFlowControl b)
356 public static bool operator !=(AsyncFlowControl a, AsyncFlowControl b)