C# 如何检查AsyncLocal<;T>;在同一个“目录”中访问;异步上下文;

C# 如何检查AsyncLocal<;T>;在同一个“目录”中访问;异步上下文;,c#,.net,async-await,task-parallel-library,C#,.net,Async Await,Task Parallel Library,TL;如果Thread.CurrentThread保持不变,则DRThreadLocal.Value指向相同的位置。AsyncLocal.Value是否有类似的功能(例如SynchronizationContext.Current或ExecutionContext.Capture()可以满足所有场景) 假设我们创建了数据结构的快照,该快照保存在线程本地存储(例如ThreadLocal实例)中,并将其传递给A腋窝类供以后使用。该类用于将此数据结构恢复到快照状态。我们不想将这个快照恢复到不同的线程

TL;如果
Thread.CurrentThread
保持不变,则DR
ThreadLocal.Value
指向相同的位置。
AsyncLocal.Value
是否有类似的功能(例如
SynchronizationContext.Current
ExecutionContext.Capture()
可以满足所有场景)


假设我们创建了数据结构的快照,该快照保存在线程本地存储(例如
ThreadLocal
实例)中,并将其传递给A腋窝类供以后使用。该类用于将此数据结构恢复到快照状态。我们不想将这个快照恢复到不同的线程上,所以我们可以检查创建了哪个线程类。例如:

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;
        }
    }
}
类存储
{
私人储户;
公共IDisposable推送(T项)
{
var bookmark=新存储书签(此);
stackHolder.Value=stackHolder.Value.Push(项目);
返回书签;
}
私有类存储书签:IDisposable
{
私人仓库所有者;
私有ImmutableStack快照;
私有线程边界线程;
公共存储书签(存储所有者)
{ 
this.owner=所有者;
this.snapshot=owner.stackHolder.Value;
this.boundThread=Thread.CurrentThread;
}
公共空间处置()
{
if(Thread.CurrentThread!=boundThread)
抛出新的InvalidOperationException(“书签交叉线程边界”);
owner.stackHolder.Value=快照;
}
}
}
通过这种方式,我们基本上将StorageBookmark绑定到特定的线程,从而绑定到ThreadLocal存储中数据结构的特定版本。在
thread.CurrentThread

现在,我想提出一个问题。我们如何使用
asynchlocal
而不是
ThreadLocal
实现相同的行为?确切地说,有没有类似于
Thread.CurrentThread
的东西可以在构建和使用时检查,以控制“异步上下文”没有被交叉(这意味着
AsyncLocal.Value
将指向与构建书签时相同的对象)。

似乎同步上下文.Current或ExecutionContext.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();
        }
    }
}
类存储
{
私有AsyncLocal stackHolder=new AsyncLocal();
公共IDisposable推送(T项)
{
var bookmark=新存储书签(此);
stackHolder.Value=(stackHolder.Value??ImmutableStack.Empty)。推送(项目);
返回书签;
}
私有类存储书签:IDisposable
{
私人仓库所有者;
私有ImmutableStack快照;
私有线程边界线程;
私有只读对象id;
公共存储书签(存储所有者)
{
id=新对象();
this.owner=所有者;
this.snapshot=owner.stackHolder.Value;
LogicalSetData(“异步存储”,id);
}
公共空间处置()
{
if(CallContext.LogicalGetData(“异步存储”)!=id)
抛出新的InvalidOperationException(“书签跨越异步上下文边界”);
owner.stackHolder.Value=快照;
}
}
}
公共课程
{
静态void Main()
{
DoesNotThrow().Wait();
抛出()等待();
}
静态异步任务DoesNotThrow()
{
var storage=新存储();
使用(storage.Push(“hello”))
{
等待任务;
}
}
静态异步任务抛出()
{
var storage=新存储();
var disposable=storage.Push(“hello”);
使用(ExecutionContext.SuppressFlow())
{
Task.Run(()=>{disposable.Dispose();}).Wait();
}
}
}

您希望做的事情与异步执行上下文的本质完全相反;您不需要(因此也不能保证)立即等待在异步上下文中创建的所有任务,等待的顺序与创建任务的顺序相同,或者根本不需要等待,但是在调用上下文的范围内创建任务会使它们成为同一异步上下文周期的一部分

将异步执行上下文视为不同于线程上下文可能很有挑战性,但异步并不是并行的同义词,而并行正是逻辑线程所支持的。存储在线程本地存储中的、不打算跨线程共享/复制的对象通常是可变的,因为逻辑线程内的执行始终能够保证相对受限的顺序逻辑(如果为了确保编译时优化不会给您带来麻烦,可能需要进行一些特殊处理,尽管这很少见,而且只在非常特定的场景中才需要)。因此,示例中的
ThreadLocal
实际上不需要是
ImmutableStack
,它可能只是一个
堆栈
(哪个性能更好)因为您不需要担心写时复制或并发访问。如果堆栈是可公开访问的,那么更重要的是有人可以将堆栈传递给其他线程,这些线程可以推送/弹出项目,但由于这是一个私有实现细节,因此
ImmutableStack
实际上可能被视为不必要的comp灵活性

不管怎样,E