AsyncLocal.cs 22 KB

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