TL;DR ThreadLocal<T>.Value 指向相同的位置,如果 Thread.CurrentThread 保持不变。 AsyncLocal<T>.Value 是否有类似的东西(例如,SychronizationContext.CurrentExecutionContext.Capture() 是否适用于所有场景)?

想象一下,我们已经创建了一些数据结构的快照,该快照保存在线程本地存储(例如 ThreadLocal<T> 实例)中,并将其传递给辅助类以供以后使用。这个辅助类用于将这个数据结构恢复到快照状态。我们不想将此快照恢复到不同的线程上,因此我们可以检查创建了哪个线程辅助类。例如:

class Storage<T>
{
    private ThreadLocal<ImmutableStack<T>> stackHolder;

    public IDisposable Push(T item)
    {
        var bookmark = new StorageBookmark<T>(this);
        stackHolder.Value = stackHolder.Value.Push(item);
        return bookmark;
    }

    private class StorageBookmark<TInner> :IDisposable
    {
        private Storage<TInner> owner;
        private ImmutableStack<TInner> snapshot;
        private Thread boundThread;

        public StorageBookmark(Storage<TInner> owner)
        {
             this.owner = owner;
             this.snapshot = owner.stackHolder.Value;
             this.boundThread  = Thread.CurrentThread;
        }

        public void Dispose()
        {
             if(Thread.CurrentThread != boundThread)
                 throw new InvalidOperationException ("Bookmark crossed thread boundary");
             owner.stackHolder.Value = snapshot;
        }
    }
}

有了这个,我们基本上将 StorageBookmark 绑定(bind)到特定线程,因此,绑定(bind)到 ThreadLocal 存储中特定版本的数据结构。我们通过在 Thread.CurrentThread 的帮助下确保我们不会跨越“线程上下文”来做到这一点
现在,手头的问题。我们如何使用 AsyncLocal<T> 而不是 ThreadLocal<T> 实现相同的行为?准确地说,是否有类似于 Thread.CurrentThread 的东西可以在构建和使用时进行检查,以控制“异步上下文”没有被跨越(这意味着 AsyncLocal<T>.Value 将指向与构建书签时相同的对象)。
似乎 SynchronizationContext.CurrentExecutionContext.Capture() 可能就足够了,但我不确定哪个更好,并且没有捕获(或者甚至可以在所有可能的情况下工作)

最佳答案

逻辑调用上下文与执行上下文具有相同的流语义,因此与 AsyncLocal 相同。知道了这一点,您可以在逻辑上下文中存储一个值以检测何时跨越“异步上下文”边界:

class Storage<T>
{
    private AsyncLocal<ImmutableStack<T>> stackHolder = new AsyncLocal<ImmutableStack<T>>();

    public IDisposable Push(T item)
    {
        var bookmark = new StorageBookmark<T>(this);

        stackHolder.Value = (stackHolder.Value ?? ImmutableStack<T>.Empty).Push(item);
        return bookmark;
    }

    private class StorageBookmark<TInner> : IDisposable
    {
        private Storage<TInner> owner;
        private ImmutableStack<TInner> snapshot;
        private Thread boundThread;
        private readonly object id;

        public StorageBookmark(Storage<TInner> owner)
        {
            id = new object();
            this.owner = owner;
            this.snapshot = owner.stackHolder.Value;
            CallContext.LogicalSetData("AsyncStorage", id);
        }

        public void Dispose()
        {
            if (CallContext.LogicalGetData("AsyncStorage") != id)
                throw new InvalidOperationException("Bookmark crossed async context boundary");
            owner.stackHolder.Value = snapshot;
        }
    }
}

public class Program
{
    static void Main()
    {
        DoesNotThrow().Wait();
        Throws().Wait();
    }

    static async Task DoesNotThrow()
    {
        var storage = new Storage<string>();

        using (storage.Push("hello"))
        {
            await Task.Yield();
        }
    }

    static async Task Throws()
    {
        var storage = new Storage<string>();

        var disposable = storage.Push("hello");

        using (ExecutionContext.SuppressFlow())
        {
            Task.Run(() => { disposable.Dispose(); }).Wait();
        }
    }
}

关于c# - 如何检查 AsyncLocal<T> 是否在同一个 "async context"内被访问,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48224335/

10-15 15:52