将 AsyncLocal 流向 WinUI 中的 UI 线程 3

Flowing AsyncLocal to UI thread in WinUI 3

我正在使用 MainWindow 的 DispatcherQueue 将工作分派给 UI 线程。但是当我这样做时,我丢失了存储在 AsyncLocal 中的值。它似乎不会自动流动执行上下文,但我也担心如果我手动流动它会发生什么,因为那样也会流动同步上下文、区域设置信息等。

所以我的问题是,确保 AsyncLocal 变量流向 UI 线程的安全方法是什么?

我想从事的工作示例:

var asyncLocal = new AsyncLocal<int>();
asyncLocal.Value = 10;

var dispatcherQueue = _mainWindow.DispatcherQueue;
var taskCompletionSource = new TaskCompletionSource();

dispatcherQueue.TryEnqueue(new DispatcherQueueHandler(() =>
{
    Debug.Assert(asyncLocal.Value == 10);
    asyncLocal.Value = 20;
    Debug.Assert(asyncLocal.Value == 20);

    taskCompletionSource.SetResult();
}));

await taskCompletionSource.Task;
Debug.Assert(asyncLocal.Value == 10);

我还注意到堆栈跟踪丢失了,因为执行上下文没有流动,如果能够解决这个问题也很好。

简短的回答是你不能。您不能,因为将操作排队到 DispatcherQueue 不是异步流程的一部分。它基本上向 UI 线程发送消息以执行方法。

您需要在线程之间共享数据 - 异步执行线程和 UI 线程。如果你只有一个异步流,即你的异步代码不是可重入的,也不是从多个线程启动的,那么任何全局存储都可以。

如果您的代码是可重入的或可以从多个线程/任务启动,那么您需要一个可以识别其调用上下文的全局存储。例如,您可以使用 ConcurrentDictionary<Guid, MyStorageClass>.

那么你可以这样做:

var localStorage = new MyStorageClass(); //Init accordingly
localStorage.Value = 10; //Assuming your class has a property called Value

var sessionGuid = Guid.NewGuid();
concurrentStorage.TryAdd (sessionGuid, localStorage);

var dispatcherQueue = _mainWindow.DispatcherQueue;
var taskCompletionSource = new TaskCompletionSource();

dispatcherQueue.TryEnqueue(new DispatcherQueueHandler(() =>
{
    Debug.Assert(concurrentStorage[sessionGuid].Value.Value == 10);
    concurrentStorage[sessionGuid].Value.Value = 20;
    Debug.Assert(concurrentStorage[sessionGuid].Value.Value == 20);

    taskCompletionSource.SetResult();
}));

await taskCompletionSource.Task;
Debug.Assert(concurrentStorage[sessionGuid].Value.Value == 10);