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.Runtime.ExceptionServices;
16 using System.Runtime.Serialization;
18 using Thread = Internal.Runtime.Augments.RuntimeThread;
20 namespace System.Threading
22 public delegate void ContextCallback(Object state);
24 internal struct ExecutionContextSwitcher
26 internal ExecutionContext m_ec;
27 internal SynchronizationContext m_sc;
29 internal void Undo(Thread currentThread)
31 Debug.Assert(currentThread == Thread.CurrentThread);
33 // The common case is that these have not changed, so avoid the cost of a write if not needed.
34 if (currentThread.SynchronizationContext != m_sc)
36 currentThread.SynchronizationContext = m_sc;
39 if (currentThread.ExecutionContext != m_ec)
41 ExecutionContext.Restore(currentThread, m_ec);
46 public sealed class ExecutionContext : IDisposable, ISerializable
48 internal static readonly ExecutionContext Default = new ExecutionContext();
50 private readonly IAsyncLocalValueMap m_localValues;
51 private readonly IAsyncLocal[] m_localChangeNotifications;
52 private readonly bool m_isFlowSuppressed;
54 private ExecutionContext()
56 m_localValues = AsyncLocalValueMap.Empty;
57 m_localChangeNotifications = Array.Empty<IAsyncLocal>();
60 private ExecutionContext(
61 IAsyncLocalValueMap localValues,
62 IAsyncLocal[] localChangeNotifications,
63 bool isFlowSuppressed)
65 m_localValues = localValues;
66 m_localChangeNotifications = localChangeNotifications;
67 m_isFlowSuppressed = isFlowSuppressed;
70 public void GetObjectData(SerializationInfo info, StreamingContext context)
72 throw new PlatformNotSupportedException();
75 public static ExecutionContext Capture()
77 ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
79 executionContext == null ? Default :
80 executionContext.m_isFlowSuppressed ? null :
84 private ExecutionContext ShallowClone(bool isFlowSuppressed)
86 Debug.Assert(isFlowSuppressed != m_isFlowSuppressed);
88 if (!isFlowSuppressed &&
89 m_localValues == Default.m_localValues &&
90 m_localChangeNotifications == Default.m_localChangeNotifications)
92 return null; // implies the default context
94 return new ExecutionContext(m_localValues, m_localChangeNotifications, isFlowSuppressed);
97 public static AsyncFlowControl SuppressFlow()
99 Thread currentThread = Thread.CurrentThread;
100 ExecutionContext executionContext = currentThread.ExecutionContext ?? Default;
101 if (executionContext.m_isFlowSuppressed)
103 throw new InvalidOperationException(SR.InvalidOperation_CannotSupressFlowMultipleTimes);
106 executionContext = executionContext.ShallowClone(isFlowSuppressed: true);
107 var asyncFlowControl = new AsyncFlowControl();
108 currentThread.ExecutionContext = executionContext;
109 asyncFlowControl.Initialize(currentThread);
110 return asyncFlowControl;
113 public static void RestoreFlow()
115 Thread currentThread = Thread.CurrentThread;
116 ExecutionContext executionContext = currentThread.ExecutionContext;
117 if (executionContext == null || !executionContext.m_isFlowSuppressed)
119 throw new InvalidOperationException(SR.InvalidOperation_CannotRestoreUnsupressedFlow);
122 currentThread.ExecutionContext = executionContext.ShallowClone(isFlowSuppressed: false);
125 public static bool IsFlowSuppressed()
127 ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
128 return executionContext != null && executionContext.m_isFlowSuppressed;
131 public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state)
133 if (executionContext == null)
134 throw new InvalidOperationException(SR.InvalidOperation_NullContext);
136 Thread currentThread = Thread.CurrentThread;
137 ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher);
140 EstablishCopyOnWriteScope(currentThread, ref ecsw);
141 ExecutionContext.Restore(currentThread, executionContext);
146 // Note: we have a "catch" rather than a "finally" because we want
147 // to stop the first pass of EH here. That way we can restore the previous
148 // context before any of our callers' EH filters run. That means we need to
149 // end the scope separately in the non-exceptional case below.
150 ecsw.Undo(currentThread);
153 ecsw.Undo(currentThread);
156 internal static void Restore(Thread currentThread, ExecutionContext executionContext)
158 Debug.Assert(currentThread == Thread.CurrentThread);
160 ExecutionContext previous = currentThread.ExecutionContext ?? Default;
161 currentThread.ExecutionContext = executionContext;
163 // New EC could be null if that's what ECS.Undo saved off.
164 // For the purposes of dealing with context change, treat this as the default EC
165 executionContext = executionContext ?? Default;
167 if (previous != executionContext)
169 OnContextChanged(previous, executionContext);
173 internal static void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw)
175 Debug.Assert(currentThread == Thread.CurrentThread);
177 ecsw.m_ec = currentThread.ExecutionContext;
178 ecsw.m_sc = currentThread.SynchronizationContext;
181 private static void OnContextChanged(ExecutionContext previous, ExecutionContext current)
183 Debug.Assert(previous != null);
184 Debug.Assert(current != null);
185 Debug.Assert(previous != current);
187 foreach (IAsyncLocal local in previous.m_localChangeNotifications)
189 object previousValue;
191 previous.m_localValues.TryGetValue(local, out previousValue);
192 current.m_localValues.TryGetValue(local, out currentValue);
194 if (previousValue != currentValue)
195 local.OnValueChanged(previousValue, currentValue, true);
198 if (current.m_localChangeNotifications != previous.m_localChangeNotifications)
202 foreach (IAsyncLocal local in current.m_localChangeNotifications)
204 // If the local has a value in the previous context, we already fired the event for that local
205 // in the code above.
206 object previousValue;
207 if (!previous.m_localValues.TryGetValue(local, out previousValue))
210 current.m_localValues.TryGetValue(local, out currentValue);
212 if (previousValue != currentValue)
213 local.OnValueChanged(previousValue, currentValue, true);
219 Environment.FailFast(
220 SR.ExecutionContext_ExceptionInAsyncLocalNotification,
226 internal static object GetLocalValue(IAsyncLocal local)
228 ExecutionContext current = Thread.CurrentThread.ExecutionContext;
233 current.m_localValues.TryGetValue(local, out value);
237 internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications)
239 ExecutionContext current = Thread.CurrentThread.ExecutionContext ?? ExecutionContext.Default;
241 object previousValue;
242 bool hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue);
244 if (previousValue == newValue)
247 IAsyncLocalValueMap newValues = current.m_localValues.Set(local, newValue);
250 // Either copy the change notification array, or create a new one, depending on whether we need to add a new item.
252 IAsyncLocal[] newChangeNotifications = current.m_localChangeNotifications;
253 if (needChangeNotifications)
255 if (hadPreviousValue)
257 Debug.Assert(Array.IndexOf(newChangeNotifications, local) >= 0);
261 int newNotificationIndex = newChangeNotifications.Length;
262 Array.Resize(ref newChangeNotifications, newNotificationIndex + 1);
263 newChangeNotifications[newNotificationIndex] = local;
267 Thread.CurrentThread.ExecutionContext =
268 new ExecutionContext(newValues, newChangeNotifications, current.m_isFlowSuppressed);
270 if (needChangeNotifications)
272 local.OnValueChanged(previousValue, newValue, false);
276 public ExecutionContext CreateCopy()
278 return this; // since CoreCLR's ExecutionContext is immutable, we don't need to create copies.
281 public void Dispose()
283 // For CLR compat only
287 public struct AsyncFlowControl : IDisposable
289 private Thread _thread;
291 internal void Initialize(Thread currentThread)
293 Debug.Assert(currentThread == Thread.CurrentThread);
294 _thread = currentThread;
301 throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCMultiple);
303 if (Thread.CurrentThread != _thread)
305 throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCOtherThread);
308 // An async flow control cannot be undone when a different execution context is applied. The desktop framework
309 // mutates the execution context when its state changes, and only changes the instance when an execution context
310 // is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution
311 // context from being applied by returning null from ExecutionContext.Capture, so the only type of execution
312 // context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async
313 // local's value, the desktop framework verifies that a different execution context has not been applied by
314 // checking the execution context instance against the one saved from when flow was suppressed. In .NET Core,
315 // since the execution context instance will change after changing the async local's value, it verifies that a
316 // different execution context has not been applied, by instead ensuring that the current execution context's
317 // flow is suppressed.
318 if (!ExecutionContext.IsFlowSuppressed())
320 throw new InvalidOperationException(SR.InvalidOperation_AsyncFlowCtrlCtxMismatch);
324 ExecutionContext.RestoreFlow();
327 public void Dispose()
332 public override bool Equals(object obj)
334 return obj is AsyncFlowControl && Equals((AsyncFlowControl)obj);
337 public bool Equals(AsyncFlowControl obj)
339 return _thread == obj._thread;
342 public override int GetHashCode()
344 return _thread?.GetHashCode() ?? 0;
347 public static bool operator ==(AsyncFlowControl a, AsyncFlowControl b)
352 public static bool operator !=(AsyncFlowControl a, AsyncFlowControl b)