![](/img/trans.png)
[英]how to write to a file stream asynchronously using async/await from multiple threads in a thread-safe manner
[英]Is it needed to make fields thread-safe when using async/await?
有时我遇到访问对象字段的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
字段是同步的,例如在这里创建一个lock / memory-fence / barrier的是什么? 所以我的问题是; 我需要使字段是线程安全的,还是在async / await结构中处理这个问题?
编辑:澄清我的问题; 在这种情况下,应始终在同一线程上调用InternalFireQueuedAsync
。 在这种情况下,只有延续可以在不同的线程上运行,这让我想知道,我是否需要同步机制(如显式屏障)以确保值同步以避免此处描述的问题: http:// www。 albahari.com/threading/part4.aspx
编辑2:在无状态下还有一个小讨论: https : //github.com/dotnet-state-machine/stateless/issues/294
我不知道是什么确保_firing和_eventQueue字段是同步的,例如在这里创建一个lock / memory-fence / barrier的是什么? 所以我的问题是; 我需要使字段是线程安全的,还是在async / await结构中处理这个问题?
await
将确保所有必要的记忆障碍到位。 但是,这并不能使它们“线程安全”。
在这种情况下,应始终在同一线程上调用InternalFireQueuedAsync。
然后_firing
是好的,不需要volatile
或类似的东西。
但是, _eventQueue
的使用不正确。 考虑当线程池线程在await
之后恢复代码时会发生什么:完全有可能Queue<T>.Count
或Queue<T>.Dequeue()
将被线程池线程同时调用Queue<T>.Enqueue
由主线程调用。 这不是线程安全的。
如果调用InternalFireQueuedAsync
的主线程是具有单线程上下文的线程(例如UI线程),那么一个简单的解决方法是在此方法中删除ConfigureAwait(false)
所有实例。
为了安全起见,您应该将field _firing
标记为volatile
- 这将保证内存屏障,并确保可能在不同线程上运行的continuation部分将读取正确的值。 如果没有volatile
,编译器,CLR或JIT编译器,甚至CPU都可能会进行一些优化,导致代码为其读取错误的值。
对于_eventQueue
,您不修改该字段,因此将其标记为volatile
是无用的。 如果只有一个线程调用'InternalFireQueuedAsync',则不会同时从多个线程访问它,所以你没问题。
但是,如果多个线程调用InternalFireQueuedAsync
,则需要使用ConcurrentQueue
,或锁定对_eventQueue
的访问_eventQueue
。 然后,您最好还锁定对_firing
的访问_firing
,或使用Interlocked
访问它,或者将其替换为ManualResetEvent
。
ConfigureAwait(false)
表示未捕获Context以运行continuation。 使用线程池上下文并不意味着并行连续运行。 在while
循环之前和之内使用await
可确保代码(continuation)按顺序运行,因此在这种情况下无需锁定。 但是,在检查_firing
值时,您可能会遇到竞争条件 。
使用lock
或ConcurrentQueue
。
lock
解决方案:
private readonly Queue<QueuedTrigger> _eventQueue = new Queue<QueuedTrigger>();
private bool _firing;
private object _eventQueueLock = new object();
async Task InternalFireQueuedAsync(TTrigger trigger, params object[] args)
{
if (_firing)
{
lock(_eventQueueLock)
_eventQueue.Enqueue(new QueuedTrigger { Trigger = trigger, Args = args });
return;
}
try
{
_firing = true;
await InternalFireOneAsync(trigger, args).ConfigureAwait(false);
lock(_eventQueueLock)
while (_eventQueue.Count != 0)
{
var queuedEvent = _eventQueue.Dequeue();
await InternalFireOneAsync(queuedEvent.Trigger, queuedEvent.Args).ConfigureAwait(false);
}
}
finally
{
_firing = false;
}
}
ConcurrentQueue
解决方案:
private readonly ConccurentQueue<QueuedTrigger> _eventQueue = new ConccurentQueue<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);
lock(_eventQueueLock)
while (_eventQueue.Count != 0)
{
object queuedEvent; // change object > expected type
if(!_eventQueue.TryDequeue())
continue;
await InternalFireOneAsync(queuedEvent.Trigger, queuedEvent.Args).ConfigureAwait(false);
}
}
finally
{
_firing = false;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.