Fix pragma warning restore (dotnet/coreclr#26389)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Threading / AsyncLocal.cs
blobe21e14e5a0db37ac013270b8a9d04b7d72c5efa7
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 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Diagnostics.CodeAnalysis;
9 namespace System.Threading
12 // AsyncLocal<T> represents "ambient" data that is local to a given asynchronous control flow, such as an
13 // async method. For example, say you want to associate a culture with a given async flow:
15 // static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>();
17 // static async Task SomeOperationAsync(Culture culture)
18 // {
19 // s_currentCulture.Value = culture;
21 // await FooAsync();
22 // }
24 // static async Task FooAsync()
25 // {
26 // PrintStringWithCulture(s_currentCulture.Value);
27 // }
29 // AsyncLocal<T> also provides optional notifications when the value associated with the current thread
30 // changes, either because it was explicitly changed by setting the Value property, or implicitly changed
31 // when the thread encountered an "await" or other context transition. For example, we might want our
32 // current culture to be communicated to the OS as well:
34 // static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>(
35 // args =>
36 // {
37 // NativeMethods.SetThreadCulture(args.CurrentValue.LCID);
38 // });
40 public sealed class AsyncLocal<T> : IAsyncLocal
42 private readonly Action<AsyncLocalValueChangedArgs<T>>? m_valueChangedHandler;
45 // Constructs an AsyncLocal<T> that does not receive change notifications.
47 public AsyncLocal()
52 // Constructs an AsyncLocal<T> with a delegate that is called whenever the current value changes
53 // on any thread.
55 public AsyncLocal(Action<AsyncLocalValueChangedArgs<T>>? valueChangedHandler)
57 m_valueChangedHandler = valueChangedHandler;
60 [MaybeNull]
61 public T Value
63 get
65 object? obj = ExecutionContext.GetLocalValue(this);
66 return (obj == null) ? default : (T)obj;
68 set
70 ExecutionContext.SetLocalValue(this, value, m_valueChangedHandler != null);
74 void IAsyncLocal.OnValueChanged(object? previousValueObj, object? currentValueObj, bool contextChanged)
76 Debug.Assert(m_valueChangedHandler != null);
77 T previousValue = previousValueObj == null ? default! : (T)previousValueObj;
78 T currentValue = currentValueObj == null ? default! : (T)currentValueObj;
79 m_valueChangedHandler(new AsyncLocalValueChangedArgs<T>(previousValue, currentValue, contextChanged));
84 // Interface to allow non-generic code in ExecutionContext to call into the generic AsyncLocal<T> type.
86 internal interface IAsyncLocal
88 void OnValueChanged(object? previousValue, object? currentValue, bool contextChanged);
91 public readonly struct AsyncLocalValueChangedArgs<T>
93 [MaybeNull] public T PreviousValue { get; }
94 [MaybeNull] public T CurrentValue { get; }
97 // If the value changed because we changed to a different ExecutionContext, this is true. If it changed
98 // because someone set the Value property, this is false.
100 public bool ThreadContextChanged { get; }
102 internal AsyncLocalValueChangedArgs([AllowNull] T previousValue, [AllowNull] T currentValue, bool contextChanged)
104 PreviousValue = previousValue;
105 CurrentValue = currentValue;
106 ThreadContextChanged = contextChanged;
111 // Interface used to store an IAsyncLocal => object mapping in ExecutionContext.
112 // Implementations are specialized based on the number of elements in the immutable
113 // map in order to minimize memory consumption and look-up times.
115 internal interface IAsyncLocalValueMap
117 bool TryGetValue(IAsyncLocal key, out object? value);
118 IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent);
122 // Utility functions for getting/creating instances of IAsyncLocalValueMap
124 internal static class AsyncLocalValueMap
126 public static IAsyncLocalValueMap Empty { get; } = new EmptyAsyncLocalValueMap();
128 public static bool IsEmpty(IAsyncLocalValueMap asyncLocalValueMap)
130 Debug.Assert(asyncLocalValueMap != null);
131 Debug.Assert(asyncLocalValueMap == Empty || asyncLocalValueMap.GetType() != typeof(EmptyAsyncLocalValueMap));
133 return asyncLocalValueMap == Empty;
136 public static IAsyncLocalValueMap Create(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
138 // If the value isn't null or a null value may not be treated as nonexistent, then create a new one-element map
139 // to store the key/value pair. Otherwise, use the empty map.
140 return value != null || !treatNullValueAsNonexistent ?
141 new OneElementAsyncLocalValueMap(key, value) :
142 Empty;
145 // Instance without any key/value pairs. Used as a singleton/
146 private sealed class EmptyAsyncLocalValueMap : IAsyncLocalValueMap
148 public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
150 // If the value isn't null or a null value may not be treated as nonexistent, then create a new one-element map
151 // to store the key/value pair. Otherwise, use the empty map.
152 return value != null || !treatNullValueAsNonexistent ?
153 new OneElementAsyncLocalValueMap(key, value) :
154 (IAsyncLocalValueMap)this;
157 public bool TryGetValue(IAsyncLocal key, out object? value)
159 value = null;
160 return false;
164 // Instance with one key/value pair.
165 private sealed class OneElementAsyncLocalValueMap : IAsyncLocalValueMap
167 private readonly IAsyncLocal _key1;
168 private readonly object? _value1;
170 public OneElementAsyncLocalValueMap(IAsyncLocal key, object? value)
172 _key1 = key; _value1 = value;
175 public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
177 if (value != null || !treatNullValueAsNonexistent)
179 // If the key matches one already contained in this map, then create a new one-element map with the updated
180 // value, otherwise create a two-element map with the additional key/value.
181 return ReferenceEquals(key, _key1) ?
182 new OneElementAsyncLocalValueMap(key, value) :
183 (IAsyncLocalValueMap)new TwoElementAsyncLocalValueMap(_key1, _value1, key, value);
185 else
187 // If the key exists in this map, remove it by downgrading to an empty map. Otherwise, there's nothing to
188 // add or remove, so just return this map.
189 return ReferenceEquals(key, _key1) ?
190 Empty :
191 (IAsyncLocalValueMap)this;
195 public bool TryGetValue(IAsyncLocal key, out object? value)
197 if (ReferenceEquals(key, _key1))
199 value = _value1;
200 return true;
202 else
204 value = null;
205 return false;
210 // Instance with two key/value pairs.
211 private sealed class TwoElementAsyncLocalValueMap : IAsyncLocalValueMap
213 private readonly IAsyncLocal _key1, _key2;
214 private readonly object? _value1, _value2;
216 public TwoElementAsyncLocalValueMap(IAsyncLocal key1, object? value1, IAsyncLocal key2, object? value2)
218 _key1 = key1; _value1 = value1;
219 _key2 = key2; _value2 = value2;
222 public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
224 if (value != null || !treatNullValueAsNonexistent)
226 // If the key matches one already contained in this map, then create a new two-element map with the updated
227 // value, otherwise create a three-element map with the additional key/value.
228 return
229 ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(key, value, _key2, _value2) :
230 ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, key, value) :
231 (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value);
233 else
235 // If the key exists in this map, remove it by downgrading to a one-element map without the key. Otherwise,
236 // there's nothing to add or remove, so just return this map.
237 return
238 ReferenceEquals(key, _key1) ? new OneElementAsyncLocalValueMap(_key2, _value2) :
239 ReferenceEquals(key, _key2) ? new OneElementAsyncLocalValueMap(_key1, _value1) :
240 (IAsyncLocalValueMap)this;
244 public bool TryGetValue(IAsyncLocal key, out object? value)
246 if (ReferenceEquals(key, _key1))
248 value = _value1;
249 return true;
251 else if (ReferenceEquals(key, _key2))
253 value = _value2;
254 return true;
256 else
258 value = null;
259 return false;
264 // Instance with three key/value pairs.
265 private sealed class ThreeElementAsyncLocalValueMap : IAsyncLocalValueMap
267 private readonly IAsyncLocal _key1, _key2, _key3;
268 private readonly object? _value1, _value2, _value3;
270 public ThreeElementAsyncLocalValueMap(IAsyncLocal key1, object? value1, IAsyncLocal key2, object? value2, IAsyncLocal key3, object? value3)
272 _key1 = key1; _value1 = value1;
273 _key2 = key2; _value2 = value2;
274 _key3 = key3; _value3 = value3;
277 public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
279 if (value != null || !treatNullValueAsNonexistent)
281 // If the key matches one already contained in this map, then create a new three-element map with the
282 // updated value.
283 if (ReferenceEquals(key, _key1)) return new ThreeElementAsyncLocalValueMap(key, value, _key2, _value2, _key3, _value3);
284 if (ReferenceEquals(key, _key2)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, key, value, _key3, _value3);
285 if (ReferenceEquals(key, _key3)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value);
287 // The key doesn't exist in this map, so upgrade to a multi map that contains
288 // the additional key/value pair.
289 var multi = new MultiElementAsyncLocalValueMap(4);
290 multi.UnsafeStore(0, _key1, _value1);
291 multi.UnsafeStore(1, _key2, _value2);
292 multi.UnsafeStore(2, _key3, _value3);
293 multi.UnsafeStore(3, key, value);
294 return multi;
296 else
298 // If the key exists in this map, remove it by downgrading to a two-element map without the key. Otherwise,
299 // there's nothing to add or remove, so just return this map.
300 return
301 ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(_key2, _value2, _key3, _value3) :
302 ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, _key3, _value3) :
303 ReferenceEquals(key, _key3) ? new TwoElementAsyncLocalValueMap(_key1, _value1, _key2, _value2) :
304 (IAsyncLocalValueMap)this;
308 public bool TryGetValue(IAsyncLocal key, out object? value)
310 if (ReferenceEquals(key, _key1))
312 value = _value1;
313 return true;
315 else if (ReferenceEquals(key, _key2))
317 value = _value2;
318 return true;
320 else if (ReferenceEquals(key, _key3))
322 value = _value3;
323 return true;
325 else
327 value = null;
328 return false;
333 // Instance with up to 16 key/value pairs.
334 private sealed class MultiElementAsyncLocalValueMap : IAsyncLocalValueMap
336 internal const int MaxMultiElements = 16;
337 private readonly KeyValuePair<IAsyncLocal, object?>[] _keyValues;
339 internal MultiElementAsyncLocalValueMap(int count)
341 Debug.Assert(count <= MaxMultiElements);
342 _keyValues = new KeyValuePair<IAsyncLocal, object?>[count];
345 internal void UnsafeStore(int index, IAsyncLocal key, object? value)
347 Debug.Assert(index < _keyValues.Length);
348 _keyValues[index] = new KeyValuePair<IAsyncLocal, object?>(key, value);
351 public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
353 // Find the key in this map.
354 for (int i = 0; i < _keyValues.Length; i++)
356 if (ReferenceEquals(key, _keyValues[i].Key))
358 // The key is in the map.
359 if (value != null || !treatNullValueAsNonexistent)
361 // Create a new map of the same size that has all of the same pairs, with this new key/value pair
362 // overwriting the old.
363 var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length);
364 Array.Copy(_keyValues, 0, multi._keyValues, 0, _keyValues.Length);
365 multi._keyValues[i] = new KeyValuePair<IAsyncLocal, object?>(key, value);
366 return multi;
368 else if (_keyValues.Length == 4)
370 // We only have four elements, one of which we're removing, so downgrade to a three-element map,
371 // without the matching element.
372 return
373 i == 0 ? new ThreeElementAsyncLocalValueMap(_keyValues[1].Key, _keyValues[1].Value, _keyValues[2].Key, _keyValues[2].Value, _keyValues[3].Key, _keyValues[3].Value) :
374 i == 1 ? new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[2].Key, _keyValues[2].Value, _keyValues[3].Key, _keyValues[3].Value) :
375 i == 2 ? new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[1].Key, _keyValues[1].Value, _keyValues[3].Key, _keyValues[3].Value) :
376 (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[1].Key, _keyValues[1].Value, _keyValues[2].Key, _keyValues[2].Value);
378 else
380 // We have enough elements remaining to warrant a multi map. Create a new one and copy all of the
381 // elements from this one, except the one to be removed.
382 var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length - 1);
383 if (i != 0) Array.Copy(_keyValues, 0, multi._keyValues, 0, i);
384 if (i != _keyValues.Length - 1) Array.Copy(_keyValues, i + 1, multi._keyValues, i, _keyValues.Length - i - 1);
385 return multi;
390 // The key does not already exist in this map.
392 if (value == null && treatNullValueAsNonexistent)
394 // We can simply return this same map, as there's nothing to add or remove.
395 return this;
398 // We need to create a new map that has the additional key/value pair.
399 // If with the addition we can still fit in a multi map, create one.
400 if (_keyValues.Length < MaxMultiElements)
402 var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length + 1);
403 Array.Copy(_keyValues, 0, multi._keyValues, 0, _keyValues.Length);
404 multi._keyValues[_keyValues.Length] = new KeyValuePair<IAsyncLocal, object?>(key, value);
405 return multi;
408 // Otherwise, upgrade to a many map.
409 var many = new ManyElementAsyncLocalValueMap(MaxMultiElements + 1);
410 foreach (KeyValuePair<IAsyncLocal, object?> pair in _keyValues)
412 many[pair.Key] = pair.Value;
414 many[key] = value;
415 return many;
418 public bool TryGetValue(IAsyncLocal key, out object? value)
420 foreach (KeyValuePair<IAsyncLocal, object?> pair in _keyValues)
422 if (ReferenceEquals(key, pair.Key))
424 value = pair.Value;
425 return true;
428 value = null;
429 return false;
433 // Instance with any number of key/value pairs.
434 private sealed class ManyElementAsyncLocalValueMap : Dictionary<IAsyncLocal, object?>, IAsyncLocalValueMap
436 public ManyElementAsyncLocalValueMap(int capacity) : base(capacity) { }
438 public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
440 int count = Count;
441 bool containsKey = ContainsKey(key);
443 // If the value being set exists, create a new many map, copy all of the elements from this one,
444 // and then store the new key/value pair into it. This is the most common case.
445 if (value != null || !treatNullValueAsNonexistent)
447 var map = new ManyElementAsyncLocalValueMap(count + (containsKey ? 0 : 1));
448 foreach (KeyValuePair<IAsyncLocal, object?> pair in this)
450 map[pair.Key] = pair.Value;
452 map[key] = value;
453 return map;
456 // Otherwise, the value is null and a null value may be treated as nonexistent. We can downgrade to a smaller
457 // map rather than storing null.
459 // If the key is contained in this map, we're going to create a new map that's one pair smaller.
460 if (containsKey)
462 // If the new count would be within range of a multi map instead of a many map,
463 // downgrade to the multi map, which uses less memory and is faster to access.
464 // Otherwise, just create a new many map that's missing this key.
465 if (count == MultiElementAsyncLocalValueMap.MaxMultiElements + 1)
467 var multi = new MultiElementAsyncLocalValueMap(MultiElementAsyncLocalValueMap.MaxMultiElements);
468 int index = 0;
469 foreach (KeyValuePair<IAsyncLocal, object?> pair in this)
471 if (!ReferenceEquals(key, pair.Key))
473 multi.UnsafeStore(index++, pair.Key, pair.Value);
476 Debug.Assert(index == MultiElementAsyncLocalValueMap.MaxMultiElements);
477 return multi;
479 else
481 var map = new ManyElementAsyncLocalValueMap(count - 1);
482 foreach (KeyValuePair<IAsyncLocal, object?> pair in this)
484 if (!ReferenceEquals(key, pair.Key))
486 map[pair.Key] = pair.Value;
489 Debug.Assert(map.Count == count - 1);
490 return map;
494 // We were storing null and a null value may be treated as nonexistent, but the key wasn't in the map, so
495 // there's nothing to change. Just return this instance.
496 return this;