AsyncLocal.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  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.
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.Diagnostics.CodeAnalysis;
  7. namespace System.Threading
  8. {
  9. //
  10. // AsyncLocal<T> represents "ambient" data that is local to a given asynchronous control flow, such as an
  11. // async method. For example, say you want to associate a culture with a given async flow:
  12. //
  13. // static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>();
  14. //
  15. // static async Task SomeOperationAsync(Culture culture)
  16. // {
  17. // s_currentCulture.Value = culture;
  18. //
  19. // await FooAsync();
  20. // }
  21. //
  22. // static async Task FooAsync()
  23. // {
  24. // PrintStringWithCulture(s_currentCulture.Value);
  25. // }
  26. //
  27. // AsyncLocal<T> also provides optional notifications when the value associated with the current thread
  28. // changes, either because it was explicitly changed by setting the Value property, or implicitly changed
  29. // when the thread encountered an "await" or other context transition. For example, we might want our
  30. // current culture to be communicated to the OS as well:
  31. //
  32. // static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>(
  33. // args =>
  34. // {
  35. // NativeMethods.SetThreadCulture(args.CurrentValue.LCID);
  36. // });
  37. //
  38. public sealed class AsyncLocal<T> : IAsyncLocal
  39. {
  40. private readonly Action<AsyncLocalValueChangedArgs<T>>? m_valueChangedHandler;
  41. //
  42. // Constructs an AsyncLocal<T> that does not receive change notifications.
  43. //
  44. public AsyncLocal()
  45. {
  46. }
  47. //
  48. // Constructs an AsyncLocal<T> with a delegate that is called whenever the current value changes
  49. // on any thread.
  50. //
  51. public AsyncLocal(Action<AsyncLocalValueChangedArgs<T>>? valueChangedHandler)
  52. {
  53. m_valueChangedHandler = valueChangedHandler;
  54. }
  55. [MaybeNull]
  56. public T Value
  57. {
  58. get
  59. {
  60. object? obj = ExecutionContext.GetLocalValue(this);
  61. return (obj == null) ? default : (T)obj;
  62. }
  63. set => ExecutionContext.SetLocalValue(this, value, m_valueChangedHandler != null);
  64. }
  65. void IAsyncLocal.OnValueChanged(object? previousValueObj, object? currentValueObj, bool contextChanged)
  66. {
  67. Debug.Assert(m_valueChangedHandler != null);
  68. T previousValue = previousValueObj == null ? default! : (T)previousValueObj;
  69. T currentValue = currentValueObj == null ? default! : (T)currentValueObj;
  70. m_valueChangedHandler(new AsyncLocalValueChangedArgs<T>(previousValue, currentValue, contextChanged));
  71. }
  72. }
  73. //
  74. // Interface to allow non-generic code in ExecutionContext to call into the generic AsyncLocal<T> type.
  75. //
  76. internal interface IAsyncLocal
  77. {
  78. void OnValueChanged(object? previousValue, object? currentValue, bool contextChanged);
  79. }
  80. public readonly struct AsyncLocalValueChangedArgs<T>
  81. {
  82. [MaybeNull] public T PreviousValue { get; }
  83. [MaybeNull] public T CurrentValue { get; }
  84. //
  85. // If the value changed because we changed to a different ExecutionContext, this is true. If it changed
  86. // because someone set the Value property, this is false.
  87. //
  88. public bool ThreadContextChanged { get; }
  89. internal AsyncLocalValueChangedArgs([AllowNull] T previousValue, [AllowNull] T currentValue, bool contextChanged)
  90. {
  91. PreviousValue = previousValue;
  92. CurrentValue = currentValue;
  93. ThreadContextChanged = contextChanged;
  94. }
  95. }
  96. //
  97. // Interface used to store an IAsyncLocal => object mapping in ExecutionContext.
  98. // Implementations are specialized based on the number of elements in the immutable
  99. // map in order to minimize memory consumption and look-up times.
  100. //
  101. internal interface IAsyncLocalValueMap
  102. {
  103. bool TryGetValue(IAsyncLocal key, out object? value);
  104. IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent);
  105. }
  106. //
  107. // Utility functions for getting/creating instances of IAsyncLocalValueMap
  108. //
  109. internal static class AsyncLocalValueMap
  110. {
  111. public static IAsyncLocalValueMap Empty { get; } = new EmptyAsyncLocalValueMap();
  112. public static bool IsEmpty(IAsyncLocalValueMap asyncLocalValueMap)
  113. {
  114. Debug.Assert(asyncLocalValueMap != null);
  115. Debug.Assert(asyncLocalValueMap == Empty || asyncLocalValueMap.GetType() != typeof(EmptyAsyncLocalValueMap));
  116. return asyncLocalValueMap == Empty;
  117. }
  118. public static IAsyncLocalValueMap Create(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
  119. {
  120. // If the value isn't null or a null value may not be treated as nonexistent, then create a new one-element map
  121. // to store the key/value pair. Otherwise, use the empty map.
  122. return value != null || !treatNullValueAsNonexistent ?
  123. new OneElementAsyncLocalValueMap(key, value) :
  124. Empty;
  125. }
  126. // Instance without any key/value pairs. Used as a singleton/
  127. private sealed class EmptyAsyncLocalValueMap : IAsyncLocalValueMap
  128. {
  129. public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
  130. {
  131. // If the value isn't null or a null value may not be treated as nonexistent, then create a new one-element map
  132. // to store the key/value pair. Otherwise, use the empty map.
  133. return value != null || !treatNullValueAsNonexistent ?
  134. new OneElementAsyncLocalValueMap(key, value) :
  135. (IAsyncLocalValueMap)this;
  136. }
  137. public bool TryGetValue(IAsyncLocal key, out object? value)
  138. {
  139. value = null;
  140. return false;
  141. }
  142. }
  143. // Instance with one key/value pair.
  144. private sealed class OneElementAsyncLocalValueMap : IAsyncLocalValueMap
  145. {
  146. private readonly IAsyncLocal _key1;
  147. private readonly object? _value1;
  148. public OneElementAsyncLocalValueMap(IAsyncLocal key, object? value)
  149. {
  150. _key1 = key; _value1 = value;
  151. }
  152. public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
  153. {
  154. if (value != null || !treatNullValueAsNonexistent)
  155. {
  156. // If the key matches one already contained in this map, then create a new one-element map with the updated
  157. // value, otherwise create a two-element map with the additional key/value.
  158. return ReferenceEquals(key, _key1) ?
  159. new OneElementAsyncLocalValueMap(key, value) :
  160. (IAsyncLocalValueMap)new TwoElementAsyncLocalValueMap(_key1, _value1, key, value);
  161. }
  162. else
  163. {
  164. // If the key exists in this map, remove it by downgrading to an empty map. Otherwise, there's nothing to
  165. // add or remove, so just return this map.
  166. return ReferenceEquals(key, _key1) ?
  167. Empty :
  168. (IAsyncLocalValueMap)this;
  169. }
  170. }
  171. public bool TryGetValue(IAsyncLocal key, out object? value)
  172. {
  173. if (ReferenceEquals(key, _key1))
  174. {
  175. value = _value1;
  176. return true;
  177. }
  178. else
  179. {
  180. value = null;
  181. return false;
  182. }
  183. }
  184. }
  185. // Instance with two key/value pairs.
  186. private sealed class TwoElementAsyncLocalValueMap : IAsyncLocalValueMap
  187. {
  188. private readonly IAsyncLocal _key1, _key2;
  189. private readonly object? _value1, _value2;
  190. public TwoElementAsyncLocalValueMap(IAsyncLocal key1, object? value1, IAsyncLocal key2, object? value2)
  191. {
  192. _key1 = key1; _value1 = value1;
  193. _key2 = key2; _value2 = value2;
  194. }
  195. public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
  196. {
  197. if (value != null || !treatNullValueAsNonexistent)
  198. {
  199. // If the key matches one already contained in this map, then create a new two-element map with the updated
  200. // value, otherwise create a three-element map with the additional key/value.
  201. return
  202. ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(key, value, _key2, _value2) :
  203. ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, key, value) :
  204. (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value);
  205. }
  206. else
  207. {
  208. // If the key exists in this map, remove it by downgrading to a one-element map without the key. Otherwise,
  209. // there's nothing to add or remove, so just return this map.
  210. return
  211. ReferenceEquals(key, _key1) ? new OneElementAsyncLocalValueMap(_key2, _value2) :
  212. ReferenceEquals(key, _key2) ? new OneElementAsyncLocalValueMap(_key1, _value1) :
  213. (IAsyncLocalValueMap)this;
  214. }
  215. }
  216. public bool TryGetValue(IAsyncLocal key, out object? value)
  217. {
  218. if (ReferenceEquals(key, _key1))
  219. {
  220. value = _value1;
  221. return true;
  222. }
  223. else if (ReferenceEquals(key, _key2))
  224. {
  225. value = _value2;
  226. return true;
  227. }
  228. else
  229. {
  230. value = null;
  231. return false;
  232. }
  233. }
  234. }
  235. // Instance with three key/value pairs.
  236. private sealed class ThreeElementAsyncLocalValueMap : IAsyncLocalValueMap
  237. {
  238. private readonly IAsyncLocal _key1, _key2, _key3;
  239. private readonly object? _value1, _value2, _value3;
  240. public ThreeElementAsyncLocalValueMap(IAsyncLocal key1, object? value1, IAsyncLocal key2, object? value2, IAsyncLocal key3, object? value3)
  241. {
  242. _key1 = key1; _value1 = value1;
  243. _key2 = key2; _value2 = value2;
  244. _key3 = key3; _value3 = value3;
  245. }
  246. public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
  247. {
  248. if (value != null || !treatNullValueAsNonexistent)
  249. {
  250. // If the key matches one already contained in this map, then create a new three-element map with the
  251. // updated value.
  252. if (ReferenceEquals(key, _key1)) return new ThreeElementAsyncLocalValueMap(key, value, _key2, _value2, _key3, _value3);
  253. if (ReferenceEquals(key, _key2)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, key, value, _key3, _value3);
  254. if (ReferenceEquals(key, _key3)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value);
  255. // The key doesn't exist in this map, so upgrade to a multi map that contains
  256. // the additional key/value pair.
  257. var multi = new MultiElementAsyncLocalValueMap(4);
  258. multi.UnsafeStore(0, _key1, _value1);
  259. multi.UnsafeStore(1, _key2, _value2);
  260. multi.UnsafeStore(2, _key3, _value3);
  261. multi.UnsafeStore(3, key, value);
  262. return multi;
  263. }
  264. else
  265. {
  266. // If the key exists in this map, remove it by downgrading to a two-element map without the key. Otherwise,
  267. // there's nothing to add or remove, so just return this map.
  268. return
  269. ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(_key2, _value2, _key3, _value3) :
  270. ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, _key3, _value3) :
  271. ReferenceEquals(key, _key3) ? new TwoElementAsyncLocalValueMap(_key1, _value1, _key2, _value2) :
  272. (IAsyncLocalValueMap)this;
  273. }
  274. }
  275. public bool TryGetValue(IAsyncLocal key, out object? value)
  276. {
  277. if (ReferenceEquals(key, _key1))
  278. {
  279. value = _value1;
  280. return true;
  281. }
  282. else if (ReferenceEquals(key, _key2))
  283. {
  284. value = _value2;
  285. return true;
  286. }
  287. else if (ReferenceEquals(key, _key3))
  288. {
  289. value = _value3;
  290. return true;
  291. }
  292. else
  293. {
  294. value = null;
  295. return false;
  296. }
  297. }
  298. }
  299. // Instance with up to 16 key/value pairs.
  300. private sealed class MultiElementAsyncLocalValueMap : IAsyncLocalValueMap
  301. {
  302. internal const int MaxMultiElements = 16;
  303. private readonly KeyValuePair<IAsyncLocal, object?>[] _keyValues;
  304. internal MultiElementAsyncLocalValueMap(int count)
  305. {
  306. Debug.Assert(count <= MaxMultiElements);
  307. _keyValues = new KeyValuePair<IAsyncLocal, object?>[count];
  308. }
  309. internal void UnsafeStore(int index, IAsyncLocal key, object? value)
  310. {
  311. Debug.Assert(index < _keyValues.Length);
  312. _keyValues[index] = new KeyValuePair<IAsyncLocal, object?>(key, value);
  313. }
  314. public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
  315. {
  316. // Find the key in this map.
  317. for (int i = 0; i < _keyValues.Length; i++)
  318. {
  319. if (ReferenceEquals(key, _keyValues[i].Key))
  320. {
  321. // The key is in the map.
  322. if (value != null || !treatNullValueAsNonexistent)
  323. {
  324. // Create a new map of the same size that has all of the same pairs, with this new key/value pair
  325. // overwriting the old.
  326. var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length);
  327. Array.Copy(_keyValues, multi._keyValues, _keyValues.Length);
  328. multi._keyValues[i] = new KeyValuePair<IAsyncLocal, object?>(key, value);
  329. return multi;
  330. }
  331. else if (_keyValues.Length == 4)
  332. {
  333. // We only have four elements, one of which we're removing, so downgrade to a three-element map,
  334. // without the matching element.
  335. return
  336. i == 0 ? new ThreeElementAsyncLocalValueMap(_keyValues[1].Key, _keyValues[1].Value, _keyValues[2].Key, _keyValues[2].Value, _keyValues[3].Key, _keyValues[3].Value) :
  337. i == 1 ? new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[2].Key, _keyValues[2].Value, _keyValues[3].Key, _keyValues[3].Value) :
  338. i == 2 ? new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[1].Key, _keyValues[1].Value, _keyValues[3].Key, _keyValues[3].Value) :
  339. (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[1].Key, _keyValues[1].Value, _keyValues[2].Key, _keyValues[2].Value);
  340. }
  341. else
  342. {
  343. // We have enough elements remaining to warrant a multi map. Create a new one and copy all of the
  344. // elements from this one, except the one to be removed.
  345. var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length - 1);
  346. if (i != 0) Array.Copy(_keyValues, multi._keyValues, i);
  347. if (i != _keyValues.Length - 1) Array.Copy(_keyValues, i + 1, multi._keyValues, i, _keyValues.Length - i - 1);
  348. return multi;
  349. }
  350. }
  351. }
  352. // The key does not already exist in this map.
  353. if (value == null && treatNullValueAsNonexistent)
  354. {
  355. // We can simply return this same map, as there's nothing to add or remove.
  356. return this;
  357. }
  358. // We need to create a new map that has the additional key/value pair.
  359. // If with the addition we can still fit in a multi map, create one.
  360. if (_keyValues.Length < MaxMultiElements)
  361. {
  362. var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length + 1);
  363. Array.Copy(_keyValues, multi._keyValues, _keyValues.Length);
  364. multi._keyValues[_keyValues.Length] = new KeyValuePair<IAsyncLocal, object?>(key, value);
  365. return multi;
  366. }
  367. // Otherwise, upgrade to a many map.
  368. var many = new ManyElementAsyncLocalValueMap(MaxMultiElements + 1);
  369. foreach (KeyValuePair<IAsyncLocal, object?> pair in _keyValues)
  370. {
  371. many[pair.Key] = pair.Value;
  372. }
  373. many[key] = value;
  374. return many;
  375. }
  376. public bool TryGetValue(IAsyncLocal key, out object? value)
  377. {
  378. foreach (KeyValuePair<IAsyncLocal, object?> pair in _keyValues)
  379. {
  380. if (ReferenceEquals(key, pair.Key))
  381. {
  382. value = pair.Value;
  383. return true;
  384. }
  385. }
  386. value = null;
  387. return false;
  388. }
  389. }
  390. // Instance with any number of key/value pairs.
  391. private sealed class ManyElementAsyncLocalValueMap : Dictionary<IAsyncLocal, object?>, IAsyncLocalValueMap
  392. {
  393. public ManyElementAsyncLocalValueMap(int capacity) : base(capacity) { }
  394. public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent)
  395. {
  396. int count = Count;
  397. bool containsKey = ContainsKey(key);
  398. // If the value being set exists, create a new many map, copy all of the elements from this one,
  399. // and then store the new key/value pair into it. This is the most common case.
  400. if (value != null || !treatNullValueAsNonexistent)
  401. {
  402. var map = new ManyElementAsyncLocalValueMap(count + (containsKey ? 0 : 1));
  403. foreach (KeyValuePair<IAsyncLocal, object?> pair in this)
  404. {
  405. map[pair.Key] = pair.Value;
  406. }
  407. map[key] = value;
  408. return map;
  409. }
  410. // Otherwise, the value is null and a null value may be treated as nonexistent. We can downgrade to a smaller
  411. // map rather than storing null.
  412. // If the key is contained in this map, we're going to create a new map that's one pair smaller.
  413. if (containsKey)
  414. {
  415. // If the new count would be within range of a multi map instead of a many map,
  416. // downgrade to the multi map, which uses less memory and is faster to access.
  417. // Otherwise, just create a new many map that's missing this key.
  418. if (count == MultiElementAsyncLocalValueMap.MaxMultiElements + 1)
  419. {
  420. var multi = new MultiElementAsyncLocalValueMap(MultiElementAsyncLocalValueMap.MaxMultiElements);
  421. int index = 0;
  422. foreach (KeyValuePair<IAsyncLocal, object?> pair in this)
  423. {
  424. if (!ReferenceEquals(key, pair.Key))
  425. {
  426. multi.UnsafeStore(index++, pair.Key, pair.Value);
  427. }
  428. }
  429. Debug.Assert(index == MultiElementAsyncLocalValueMap.MaxMultiElements);
  430. return multi;
  431. }
  432. else
  433. {
  434. var map = new ManyElementAsyncLocalValueMap(count - 1);
  435. foreach (KeyValuePair<IAsyncLocal, object?> pair in this)
  436. {
  437. if (!ReferenceEquals(key, pair.Key))
  438. {
  439. map[pair.Key] = pair.Value;
  440. }
  441. }
  442. Debug.Assert(map.Count == count - 1);
  443. return map;
  444. }
  445. }
  446. // We were storing null and a null value may be treated as nonexistent, but the key wasn't in the map, so
  447. // there's nothing to change. Just return this instance.
  448. return this;
  449. }
  450. }
  451. }
  452. }