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);
48 public sealed class ExecutionContext : IDisposable, ISerializable
50 internal static readonly ExecutionContext Default = new ExecutionContext();
52 private readonly IAsyncLocalValueMap m_localValues;
53 private readonly IAsyncLocal[] m_localChangeNotifications;
54 private readonly bool m_isFlowSuppressed;
56 private ExecutionContext()
58 m_localValues = AsyncLocalValueMap.Empty;
59 m_localChangeNotifications = Array.Empty<IAsyncLocal>();
62 private ExecutionContext(
63 IAsyncLocalValueMap localValues,
64 IAsyncLocal[] localChangeNotifications,
65 bool isFlowSuppressed)
67 m_localValues = localValues;
68 m_localChangeNotifications = localChangeNotifications;
69 m_isFlowSuppressed = isFlowSuppressed;
72 public void GetObjectData(SerializationInfo info, StreamingContext context)
76 throw new ArgumentNullException(nameof(info));
78 Contract.EndContractBlock();
81 private ExecutionContext(SerializationInfo info, StreamingContext context)
85 public static ExecutionContext Capture()
87 ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
89 executionContext == null ? Default :
90 executionContext.m_isFlowSuppressed ? null :
94 private ExecutionContext ShallowClone(bool isFlowSuppressed)
96 Debug.Assert(isFlowSuppressed != m_isFlowSuppressed);
98 if (!isFlowSuppressed &&
99 m_localValues == Default.m_localValues &&
100 m_localChangeNotifications == Default.m_localChangeNotifications)
102 return null; // implies the default context
104 return new ExecutionContext(m_localValues, m_localChangeNotifications, isFlowSuppressed);
107 public static AsyncFlowControl SuppressFlow()
109 Thread currentThread = Thread.CurrentThread;
110 ExecutionContext executionContext = currentThread.ExecutionContext ?? Default;
111 if (executionContext.m_isFlowSuppressed)
113 throw new InvalidOperationException(SR.InvalidOperation_CannotSupressFlowMultipleTimes);
115 Contract.EndContractBlock();
117 executionContext = executionContext.ShallowClone(isFlowSuppressed: true);
118 var asyncFlowControl = new AsyncFlowControl();
119 currentThread.ExecutionContext = executionContext;
120 asyncFlowControl.Initialize(currentThread);
121 return asyncFlowControl;
124 public static void RestoreFlow()
126 Thread currentThread = Thread.CurrentThread;
127 ExecutionContext executionContext = currentThread.ExecutionContext;
128 if (executionContext == null || !executionContext.m_isFlowSuppressed)
130 throw new InvalidOperationException(SR.InvalidOperation_CannotRestoreUnsupressedFlow);
132 Contract.EndContractBlock();
134 currentThread.ExecutionContext = executionContext.ShallowClone(isFlowSuppressed: false);
137 public static bool IsFlowSuppressed()
139 ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
140 return executionContext != null && executionContext.m_isFlowSuppressed;
143 public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state)
145 if (executionContext == null)
146 throw new InvalidOperationException(SR.InvalidOperation_NullContext);
148 Thread currentThread = Thread.CurrentThread;
149 ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher);
152 EstablishCopyOnWriteScope(currentThread, ref ecsw);
153 ExecutionContext.Restore(currentThread, executionContext);
158 // Note: we have a "catch" rather than a "finally" because we want
159 // to stop the first pass of EH here. That way we can restore the previous
160 // context before any of our callers' EH filters run. That means we need to
161 // end the scope separately in the non-exceptional case below.
162 ecsw.Undo(currentThread);
165 ecsw.Undo(currentThread);
168 internal static void Restore(Thread currentThread, ExecutionContext executionContext)
170 Debug.Assert(currentThread == Thread.CurrentThread);
172 ExecutionContext previous = currentThread.ExecutionContext ?? Default;
173 currentThread.ExecutionContext = executionContext;
175 // New EC could be null if that's what ECS.Undo saved off.
176 // For the purposes of dealing with context change, treat this as the default EC
177 executionContext = executionContext ?? Default;
179 if (previous != executionContext)
181 OnContextChanged(previous, executionContext);
185 internal static void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw)
187 Debug.Assert(currentThread == Thread.CurrentThread);
189 ecsw.m_ec = currentThread.ExecutionContext;
190 ecsw.m_sc = currentThread.SynchronizationContext;
193 private static void OnContextChanged(ExecutionContext previous, ExecutionContext current)
195 Debug.Assert(previous != null);
196 Debug.Assert(current != null);
197 Debug.Assert(previous != current);
199 foreach (IAsyncLocal local in previous.m_localChangeNotifications)
201 object previousValue;
203 previous.m_localValues.TryGetValue(local, out previousValue);
204 current.m_localValues.TryGetValue(local, out currentValue);
206 if (previousValue != currentValue)
207 local.OnValueChanged(previousValue, currentValue, true);
210 if (current.m_localChangeNotifications != previous.m_localChangeNotifications)
214 foreach (IAsyncLocal local in current.m_localChangeNotifications)
216 // If the local has a value in the previous context, we already fired the event for that local
217 // in the code above.
218 object previousValue;
219 if (!previous.m_localValues.TryGetValue(local, out previousValue))
222 current.m_localValues.TryGetValue(local, out currentValue);
224 if (previousValue != currentValue)
225 local.OnValueChanged(previousValue, currentValue, true);
231 Environment.FailFast(
232 SR.ExecutionContext_ExceptionInAsyncLocalNotification,
238 internal static object GetLocalValue(IAsyncLocal local)
240 ExecutionContext current = Thread.CurrentThread.ExecutionContext;
245 current.m_localValues.TryGetValue(local, out value);
249 internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications)
251 ExecutionContext current = Thread.CurrentThread.ExecutionContext ?? ExecutionContext.Default;
253 object previousValue;
254 bool hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue);
256 if (previousValue == newValue)
259 IAsyncLocalValueMap newValues = current.m_localValues.Set(local, newValue);
262 // Either copy the change notification array, or create a new one, depending on whether we need to add a new item.
264 IAsyncLocal[] newChangeNotifications = current.m_localChangeNotifications;
265 if (needChangeNotifications)
267 if (hadPreviousValue)
269 Debug.Assert(Array.IndexOf(newChangeNotifications, local) >= 0);
273 int newNotificationIndex = newChangeNotifications.Length;
274 Array.Resize(ref newChangeNotifications, newNotificationIndex + 1);
275 newChangeNotifications[newNotificationIndex] = local;
279 Thread.CurrentThread.ExecutionContext =
280 new ExecutionContext(newValues, newChangeNotifications, current.m_isFlowSuppressed);
282 if (needChangeNotifications)
284 local.OnValueChanged(previousValue, newValue, false);
288 public ExecutionContext CreateCopy()
290 return this; // since CoreCLR's ExecutionContext is immutable, we don't need to create copies.
293 public void Dispose()
295 // For CLR compat only
299 public struct AsyncFlowControl : IDisposable
301 private Thread _thread;
303 internal void Initialize(Thread currentThread)
305 Debug.Assert(currentThread == Thread.CurrentThread);
306 _thread = currentThread;
313 throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCMultiple);
315 if (Thread.CurrentThread != _thread)
317 throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCOtherThread);
320 // An async flow control cannot be undone when a different execution context is applied. The desktop framework
321 // mutates the execution context when its state changes, and only changes the instance when an execution context
322 // is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution
323 // context from being applied by returning null from ExecutionContext.Capture, so the only type of execution
324 // context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async
325 // local's value, the desktop framework verifies that a different execution context has not been applied by
326 // checking the execution context instance against the one saved from when flow was suppressed. In .NET Core,
327 // since the execution context instance will change after changing the async local's value, it verifies that a
328 // different execution context has not been applied, by instead ensuring that the current execution context's
329 // flow is suppressed.
330 if (!ExecutionContext.IsFlowSuppressed())
332 throw new InvalidOperationException(SR.InvalidOperation_AsyncFlowCtrlCtxMismatch);
334 Contract.EndContractBlock();
337 ExecutionContext.RestoreFlow();
340 public void Dispose()
345 public override bool Equals(object obj)
347 return obj is AsyncFlowControl && Equals((AsyncFlowControl)obj);
350 public bool Equals(AsyncFlowControl obj)
352 return _thread == obj._thread;
355 public override int GetHashCode()
357 return _thread?.GetHashCode() ?? 0;
360 public static bool operator ==(AsyncFlowControl a, AsyncFlowControl b)
365 public static bool operator !=(AsyncFlowControl a, AsyncFlowControl b)