并发调用时可以等待损坏的值

本文关键字:损坏 等待 调用 并发 | 更新日期: 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或其他同步机制可能是最佳方法。

asyncawait不添加任何并发保护。 如果它可以在没有asyncawait的情况下被损坏,它也会被它们损坏。

读取和写入双精度不是线程安全的,因此最好在这些周围使用锁。使用 Interlocked.COmpareExchange 将正确写入变量,但您还需要以线程安全的方式读取它,所以基本上您需要一个锁。看看埃里克·利珀特的这个答案。

是的。

因为你使用的是await,所以该方法被调用,从someService.SomeAsyncMethod()中获取Task,然后产生控制权,直到所述Task完成。

当该任务完成时,control返回(通常在调用任务的同一线程上,尽管这取决于@Reed指出的SynchronizationContext(,然后调用SomePropertyset函数。此时,所述调用可能来自多个线程,或者来自启动任务的线程以外的线程,从而导致在没有lock的情况下不可预测的获取/设置行为。