| | | 1 | | using System; |
| | | 2 | | |
| | | 3 | | namespace AmbientServices; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// A class that edits the caller context *after* an asynchronous operation completes so that subsequent calls inherit t |
| | | 7 | | /// which is not the case if the context is edited in the asynchronous operation itself. |
| | | 8 | | /// Have the asynchronous function return this type (or a <see cref="ContextMutator{T}"/>) and call <see cref="ApplyCont |
| | | 9 | | /// It is *not* possible to wrap this logic and thereby simplify calling the asynchronous function and then calling <see |
| | | 10 | | /// </summary> |
| | | 11 | | /// <remarks> |
| | | 12 | | /// <pitch>Solves the async context-mutation problem: an <see cref="System.Threading.AsyncLocal{T}"/> write made <em>ins |
| | | 13 | | /// <pledge>The mutation happens only when the <em>caller</em> invokes <see cref="ApplyContextChanges"/>, and it applies |
| | | 14 | | /// <plan>Wraps a single caller-supplied <see cref="Action"/>; the class adds no state or logic beyond signaling the cal |
| | | 15 | | /// <priority> |
| | | 16 | | /// 1. Solving the problem at all over an API that cannot be misused: the calling convention is genuinely fragile — appl |
| | | 17 | | /// 2. Naming the convention in the type over hiding it: the class carries no state or logic and exists mainly so the co |
| | | 18 | | /// </priority> |
| | | 19 | | /// </remarks> |
| | | 20 | | public sealed class ContextMutator |
| | | 21 | | { |
| | | 22 | | private readonly Action _applyContextChanges; |
| | | 23 | | |
| | | 24 | | /// <summary> |
| | | 25 | | /// Constructs the context editor with actions to be executed on return. |
| | | 26 | | /// </summary> |
| | | 27 | | /// <param name="applyContextChanges">The action to call after returning from the asynchronous function.</param> |
| | | 28 | | public ContextMutator(Action applyContextChanges) |
| | | 29 | | { |
| | | 30 | | _applyContextChanges = applyContextChanges; |
| | | 31 | | } |
| | | 32 | | /// <summary> |
| | | 33 | | /// Calls the context editing action to be executed after returning from the asynchronous function. |
| | | 34 | | /// </summary> |
| | | 35 | | public void ApplyContextChanges() |
| | | 36 | | { |
| | | 37 | | _applyContextChanges.Invoke(); |
| | | 38 | | } |
| | | 39 | | } |
| | | 40 | | |
| | | 41 | | /// <summary> |
| | | 42 | | /// A class that temporarily changes the caller context *after* an asynchronous operation completes so that subsequent c |
| | | 43 | | /// It is *not* possible to wrap this logic and thereby simplify calling the asynchronous function and then calling <see |
| | | 44 | | /// </summary> |
| | | 45 | | /// <remarks> |
| | | 46 | | /// <pitch>The scoped variant of <see cref="ContextMutator"/>: applies a context change in the caller's frame and undoes |
| | | 47 | | /// <pledge>Follows the <see cref="ContextMutator"/> calling convention — apply must be invoked by the frame that should |
| | | 48 | | /// <plan>Wraps a caller-supplied apply <see cref="Action"/> and revert <see cref="Action"/>; no other state.</plan> |
| | | 49 | | /// </remarks> |
| | | 50 | | public sealed class TemporaryContextMutator: IDisposable |
| | | 51 | | { |
| | | 52 | | private readonly Action _applyContextChanges; |
| | | 53 | | private readonly Action _revertContextChanges; |
| | | 54 | | |
| | | 55 | | /// <summary> |
| | | 56 | | /// Constructs the context editor with actions to be executed on return. |
| | | 57 | | /// </summary> |
| | | 58 | | /// <param name="applyContextChanges">The action to call after returning from the asynchronous function.</param> |
| | | 59 | | /// <param name="revertContextChanges">The action to call when the temporary changes are no longer desired.</param> |
| | | 60 | | public TemporaryContextMutator(Action applyContextChanges, Action revertContextChanges) |
| | | 61 | | { |
| | | 62 | | _applyContextChanges = applyContextChanges; |
| | | 63 | | _revertContextChanges = revertContextChanges; |
| | | 64 | | } |
| | | 65 | | /// <summary> |
| | | 66 | | /// Calls the context editing action to be executed after returning from the asynchronous function. |
| | | 67 | | /// </summary> |
| | | 68 | | /// <returns>The <see cref="TemporaryContextMutator"/> instance, in case the caller wants to chain the call to this |
| | | 69 | | public TemporaryContextMutator ApplyContextChanges() |
| | | 70 | | { |
| | | 71 | | _applyContextChanges.Invoke(); |
| | | 72 | | return this; |
| | | 73 | | } |
| | | 74 | | /// <summary> |
| | | 75 | | /// Reverts the context changes applied by <see cref="ApplyContextChanges"/>. |
| | | 76 | | /// </summary> |
| | | 77 | | public void Dispose() |
| | | 78 | | { |
| | | 79 | | _revertContextChanges.Invoke(); |
| | | 80 | | } |
| | | 81 | | } |
| | | 82 | | |
| | | 83 | | /// <summary> |
| | | 84 | | /// A class that edits the caller context *after* an asynchronous operation completes so that subsequent calls inherit t |
| | | 85 | | /// which is not the case if the context is edited in the asynchronous operation itself. |
| | | 86 | | /// Have the asynchronous function return this type (or a <see cref="ContextMutator"/>) and call <see cref="ApplyContext |
| | | 87 | | /// It is *not* possible to wrap this logic and thereby simplify calling the asynchronous function and then calling <see |
| | | 88 | | /// </summary> |
| | | 89 | | /// <remarks> |
| | | 90 | | /// <pitch>The value-returning variant of <see cref="ContextMutator"/>, for asynchronous functions that need to both mut |
| | | 91 | | /// <pledge>Identical to the <see cref="ContextMutator"/> contract — the caller's frame must invoke <see cref="ApplyCont |
| | | 92 | | /// <plan>Wraps a single caller-supplied <see cref="Func{T}"/>; no other state.</plan> |
| | | 93 | | /// </remarks> |
| | | 94 | | public sealed class ContextMutator<T> |
| | | 95 | | { |
| | | 96 | | private readonly Func<T> _applyContextChanges; |
| | | 97 | | |
| | | 98 | | /// <summary> |
| | | 99 | | /// Constructs the context editor with actions to be executed on return (and possibly dispose). |
| | | 100 | | /// </summary> |
| | | 101 | | /// <param name="applyContextChanges">The action to call after returning from the asynchronous function.</param> |
| | 2 | 102 | | public ContextMutator(Func<T> applyContextChanges) |
| | | 103 | | { |
| | 2 | 104 | | _applyContextChanges = applyContextChanges; |
| | 2 | 105 | | } |
| | | 106 | | /// <summary> |
| | | 107 | | /// Calls the context editing action to be executed after returning from the asynchronous function. |
| | | 108 | | /// </summary> |
| | | 109 | | /// <returns>The result of the function.</returns> |
| | | 110 | | public T ApplyContextChanges() |
| | | 111 | | { |
| | 2 | 112 | | return _applyContextChanges.Invoke(); |
| | | 113 | | } |
| | | 114 | | } |