AsyncLocal.cs 22 KB

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