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)
75 throw new ArgumentNullException(nameof(info));
77 Contract.EndContractBlock();
80 private ExecutionContext(SerializationInfo info, StreamingContext context)
84 public static ExecutionContext Capture()
86 ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
88 executionContext == null ? Default :
89 executionContext.m_isFlowSuppressed ? null :
93 private ExecutionContext ShallowClone(bool isFlowSuppressed)
95 Debug.Assert(isFlowSuppressed != m_isFlowSuppressed);
97 if (!isFlowSuppressed &&
98 m_localValues == Default.m_localValues &&
99 m_localChangeNotifications == Default.m_localChangeNotifications)
101 return null; // implies the default context
103 return new ExecutionContext(m_localValues, m_localChangeNotifications, isFlowSuppressed);
106 public static AsyncFlowControl SuppressFlow()
108 Thread currentThread = Thread.CurrentThread;
109 ExecutionContext executionContext = currentThread.ExecutionContext ?? Default;
110 if (executionContext.m_isFlowSuppressed)
112 throw new InvalidOperationException(SR.InvalidOperation_CannotSupressFlowMultipleTimes);
114 Contract.EndContractBlock();
116 executionContext = executionContext.ShallowClone(isFlowSuppressed: true);
117 var asyncFlowControl = new AsyncFlowControl();
118 currentThread.ExecutionContext = executionContext;
119 asyncFlowControl.Initialize(currentThread);
120 return asyncFlowControl;
123 public static void RestoreFlow()
125 Thread currentThread = Thread.CurrentThread;
126 ExecutionContext executionContext = currentThread.ExecutionContext;
127 if (executionContext == null || !executionContext.m_isFlowSuppressed)
129 throw new InvalidOperationException(SR.InvalidOperation_CannotRestoreUnsupressedFlow);
131 Contract.EndContractBlock();
133 currentThread.ExecutionContext = executionContext.ShallowClone(isFlowSuppressed: false);
136 public static bool IsFlowSuppressed()
138 ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
139 return executionContext != null && executionContext.m_isFlowSuppressed;
142 public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state)
144 if (executionContext == null)
145 throw new InvalidOperationException(SR.InvalidOperation_NullContext);
147 Thread currentThread = Thread.CurrentThread;
148 ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher);
151 EstablishCopyOnWriteScope(currentThread, ref ecsw);
152 ExecutionContext.Restore(currentThread, executionContext);
157 // Note: we have a "catch" rather than a "finally" because we want
158 // to stop the first pass of EH here. That way we can restore the previous
159 // context before any of our callers' EH filters run. That means we need to
160 // end the scope separately in the non-exceptional case below.
161 ecsw.Undo(currentThread);
164 ecsw.Undo(currentThread);
167 internal static void Restore(Thread currentThread, ExecutionContext executionContext)
169 Debug.Assert(currentThread == Thread.CurrentThread);
171 ExecutionContext previous = currentThread.ExecutionContext ?? Default;
172 currentThread.ExecutionContext = executionContext;
174 // New EC could be null if that's what ECS.Undo saved off.
175 // For the purposes of dealing with context change, treat this as the default EC
176 executionContext = executionContext ?? Default;
178 if (previous != executionContext)
180 OnContextChanged(previous, executionContext);
184 internal static void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw)
186 Debug.Assert(currentThread == Thread.CurrentThread);
188 ecsw.m_ec = currentThread.ExecutionContext;
189 ecsw.m_sc = currentThread.SynchronizationContext;
192 private static void OnContextChanged(ExecutionContext previous, ExecutionContext current)
194 Debug.Assert(previous != null);
195 Debug.Assert(current != null);
196 Debug.Assert(previous != current);
198 foreach (IAsyncLocal local in previous.m_localChangeNotifications)
200 object previousValue;
202 previous.m_localValues.TryGetValue(local, out previousValue);
203 current.m_localValues.TryGetValue(local, out currentValue);
205 if (previousValue != currentValue)
206 local.OnValueChanged(previousValue, currentValue, true);
209 if (current.m_localChangeNotifications != previous.m_localChangeNotifications)
213 foreach (IAsyncLocal local in current.m_localChangeNotifications)
215 // If the local has a value in the previous context, we already fired the event for that local
216 // in the code above.
217 object previousValue;
218 if (!previous.m_localValues.TryGetValue(local, out previousValue))
221 current.m_localValues.TryGetValue(local, out currentValue);
223 if (previousValue != currentValue)
224 local.OnValueChanged(previousValue, currentValue, true);
230 Environment.FailFast(
231 SR.ExecutionContext_ExceptionInAsyncLocalNotification,
237 internal static object GetLocalValue(IAsyncLocal local)
239 ExecutionContext current = Thread.CurrentThread.ExecutionContext;
244 current.m_localValues.TryGetValue(local, out value);
248 internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications)
250 ExecutionContext current = Thread.CurrentThread.ExecutionContext ?? ExecutionContext.Default;
252 object previousValue;
253 bool hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue);
255 if (previousValue == newValue)
258 IAsyncLocalValueMap newValues = current.m_localValues.Set(local, newValue);
261 // Either copy the change notification array, or create a new one, depending on whether we need to add a new item.
263 IAsyncLocal[] newChangeNotifications = current.m_localChangeNotifications;
264 if (needChangeNotifications)
266 if (hadPreviousValue)
268 Debug.Assert(Array.IndexOf(newChangeNotifications, local) >= 0);
272 int newNotificationIndex = newChangeNotifications.Length;
273 Array.Resize(ref newChangeNotifications, newNotificationIndex + 1);
274 newChangeNotifications[newNotificationIndex] = local;
278 Thread.CurrentThread.ExecutionContext =
279 new ExecutionContext(newValues, newChangeNotifications, current.m_isFlowSuppressed);
281 if (needChangeNotifications)
283 local.OnValueChanged(previousValue, newValue, false);
287 public ExecutionContext CreateCopy()
289 return this; // since CoreCLR's ExecutionContext is immutable, we don't need to create copies.
292 public void Dispose()
294 // For CLR compat only
298 public struct AsyncFlowControl : IDisposable
300 private Thread _thread;
302 internal void Initialize(Thread currentThread)
304 Debug.Assert(currentThread == Thread.CurrentThread);
305 _thread = currentThread;
312 throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCMultiple);
314 if (Thread.CurrentThread != _thread)
316 throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCOtherThread);
319 // An async flow control cannot be undone when a different execution context is applied. The desktop framework
320 // mutates the execution context when its state changes, and only changes the instance when an execution context
321 // is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution
322 // context from being applied by returning null from ExecutionContext.Capture, so the only type of execution
323 // context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async
324 // local's value, the desktop framework verifies that a different execution context has not been applied by
325 // checking the execution context instance against the one saved from when flow was suppressed. In .NET Core,
326 // since the execution context instance will change after changing the async local's value, it verifies that a
327 // different execution context has not been applied, by instead ensuring that the current execution context's
328 // flow is suppressed.
329 if (!ExecutionContext.IsFlowSuppressed())
331 throw new InvalidOperationException(SR.InvalidOperation_AsyncFlowCtrlCtxMismatch);
333 Contract.EndContractBlock();
336 ExecutionContext.RestoreFlow();
339 public void Dispose()
344 public override bool Equals(object obj)
346 return obj is AsyncFlowControl && Equals((AsyncFlowControl)obj);
349 public bool Equals(AsyncFlowControl obj)
351 return _thread == obj._thread;
354 public override int GetHashCode()
356 return _thread?.GetHashCode() ?? 0;
359 public static bool operator ==(AsyncFlowControl a, AsyncFlowControl b)
364 public static bool operator !=(AsyncFlowControl a, AsyncFlowControl b)