并发调用时可以等待损坏的值
本文关键字:损坏 等待 调用 并发 | 更新日期: 2023-09-27 18:34:37
简单地说,我有这样的东西:
class MyClass
{
double SomeProperty {get; private set;}
public async Task SomeMethod()
{
SomeProperty = await someService.SomeAsyncMethod();
}
}
如果我同时多次打电话SomeMethod()
,SomeProperty
会损坏吗?
是的,可能。
这与async-await
无关。您只需通过多个线程为属性设置值。该值可能会损坏,您应该使用一些同步结构(即 lock
(:
public async Task SomeMethod()
{
var temp = await someService.SomeAsyncMethod();
lock (_lock)
{
SomeProperty = temp;
}
}
或Interlocked.Exchange
(使用属性的支持字段(:
public async Task SomeMethod()
{
Interlocked.Exchange(ref _backingField, await someService.SomeAsyncMethod());
}
但是,如果此方法在 GUI 线程中运行(或具有单线程SynchronizationContext
的任何其他情况(,则不会损坏,因为只有一个线程处理该属性。
如果我同时多次调用 SomeMethod(( 会损坏某些属性吗?
潜在地,是的,尽管这取决于很多因素。
写入双精度不能保证是原子操作。 由于您同时调用此方法,因此可能会同时写入值,这可能会导致未定义的行为。
有多种机制可以处理此问题。 通常,您只需要调用服务一次,在这种情况下,您可以直接存储任务:
Task<double> backing;
double SomeProperty { get { return backing.Result; } }
MyClass() // in constructor
{
backing = someService.SomeAsyncMethod(); // Note, no await here!
}
否则,如果需要多个调用,显式lock
或其他同步机制可能是最佳方法。
async
和await
不添加任何并发保护。 如果它可以在没有async
和await
的情况下被损坏,它也会被它们损坏。
读取和写入双精度不是线程安全的,因此最好在这些周围使用锁。使用 Interlocked.COmpareExchange
将正确写入变量,但您还需要以线程安全的方式读取它,所以基本上您需要一个锁。看看埃里克·利珀特的这个答案。
是的。
因为你使用的是await
,所以该方法被调用,从someService.SomeAsyncMethod()
中获取Task
,然后产生控制权,直到所述Task
完成。
当该任务完成时,control返回(通常在调用任务的同一线程上,尽管这取决于@Reed指出的SynchronizationContext
(,然后调用SomeProperty
的set
函数。此时,所述调用可能来自多个线程,或者来自启动任务的线程以外的线程,从而导致在没有lock
的情况下不可预测的获取/设置行为。