首页 > 解决方案 > 使用 async/await 时是否需要使字段线程安全?

问题描述

有时我会遇到访问对象字段的异步/等待代码。例如,Stateless 项目中的这段代码

private readonly Queue<QueuedTrigger> _eventQueue = new Queue<QueuedTrigger>();
private bool _firing;

async Task InternalFireQueuedAsync(TTrigger trigger, params object[] args)
{
    if (_firing)
    {
        _eventQueue.Enqueue(new QueuedTrigger { Trigger = trigger, Args = args });
        return;
    }

    try
    {
        _firing = true;

        await InternalFireOneAsync(trigger, args).ConfigureAwait(false);

        while (_eventQueue.Count != 0)
        {
            var queuedEvent = _eventQueue.Dequeue();
            await InternalFireOneAsync(queuedEvent.Trigger, queuedEvent.Args).ConfigureAwait(false);
        }
    }
    finally
    {
        _firing = false;
    }
}

如果我理解正确,则await **.ConfigureAwait(false)表明在此之后执行的代码await不一定必须在相同的上下文中执行。所以while这里的循环可以在 ThreadPool 线程上执行。我看不到是什么确保_firing_eventQueue字段同步,例如什么在这里创建了锁/内存围栏/屏障?所以我的问题是;我是否需要使字段线程安全,或者异步/等待结构中的某些东西可以解决这个问题?

编辑:澄清我的问题;在这种情况下InternalFireQueuedAsync,应该始终在同一个线程上调用。在那种情况下,只有延续可以在不同的线程上运行,这让我想知道,我是否需要同步机制(如显式屏障)来确保值同步以避免此处描述的问题:http://www. albahari.com/threading/part4.aspx

编辑2:在无状态也有一个小讨论: https ://github.com/dotnet-state-machine/stateless/issues/294

标签: c#multithreadingasynchronousasync-awaittask-parallel-library

解决方案


我看不到是什么确保了 _firing 和 _eventQueue 字段是同步的,例如什么在这里创建了锁/内存围栏/屏障?所以我的问题是;我是否需要使字段线程安全,或者异步/等待结构中的某些东西可以解决这个问题?

await将确保所有必要的内存屏障都到位。但是,这并不能使它们“线程安全”。

在这种情况下,应始终在同一线程上调用 InternalFireQueuedAsync。

然后_firing很好,不需要volatile或类似的东西。

但是,的用法_eventQueue不正确。考虑当线程池线程在 之后恢复代码时会发生什么await:完全有可能Queue<T>.CountQueue<T>.Dequeue()将被线程池线程调用,同时Queue<T>.Enqueue被主线程调用。这不是线程安全的。

如果主线程调用InternalFireQueuedAsync是具有单线程上下文的线程(例如 UI 线程),那么一个简单的解决方法是删除ConfigureAwait(false)此方法中的所有实例。


推荐阅读