C# 可以在并发调用时等待损坏的值
简言之,我有这样的想法:C# 可以在并发调用时等待损坏的值,c#,.net,concurrency,task-parallel-library,async-await,C#,.net,Concurrency,Task Parallel Library,Async Await,简言之,我有这样的想法: class MyClass { double SomeProperty {get; private set;} public async Task SomeMethod() { SomeProperty = await someService.SomeAsyncMethod(); } } 如果我同时调用SomeMethod()多次,那么SomeProperty是否会损坏 如果我同时多次调用SomeMethod(),So
class MyClass
{
double SomeProperty {get; private set;}
public async Task SomeMethod()
{
SomeProperty = await someService.SomeAsyncMethod();
}
}
如果我同时调用SomeMethod()
多次,那么SomeProperty
是否会损坏
如果我同时多次调用SomeMethod(),SomeProperty是否会损坏
可能是的,但这取决于很多因素
写入双精度并不能保证是原子操作。由于您同时调用此方法,因此可能会同时写入值,这可能会导致未定义的行为
有各种机制来处理这个问题。通常,您只需要调用服务一次,在这种情况下,您可以直接存储任务:
Task<double> backing;
double SomeProperty { get { return backing.Result; } }
MyClass() // in constructor
{
backing = someService.SomeAsyncMethod(); // Note, no await here!
}
任务备份;
double SomeProperty{get{return backing.Result;}
MyClass()//在构造函数中
{
backing=someService.SomeAsyncMethod();//注意,这里没有等待!
}
否则,如果您需要多个调用,那么显式的锁或其他同步机制可能是最好的方法。是的,可能是
这与asyncwait
没有真正的关系。您只需通过多个线程为属性设置一个值。该值可能会损坏,您应该使用一些同步构造(即锁
):
或联锁的.Exchange
(使用属性的支持字段):
但是,如果此方法在GUI线程中运行(或在任何其他情况下使用单线程的同步上下文),则不会发生损坏,因为只有一个线程处理该属性。是
由于您使用的是wait
,因此会调用该方法,从someService.somesynchmethod()
获取任务
,然后产生控制,直到所述任务
完成
当该任务完成时,控件返回(通常在调用任务的同一线程上,尽管这取决于@Reed指出的SynchronizationContext
),然后调用SomeProperty
的set
函数。此时,所述调用可能来自多个线程,或者来自启动任务的线程以外的线程,从而导致在没有lock
s的情况下出现不可预测的get/set行为。async
和wait
不添加任何并发保护。如果它可以在没有async
和wait
的情况下被损坏,那么它也可能会被损坏。读取和写入双精度不是线程安全的,因此最好在它们周围使用锁。使用Interlocked.COmpareExchange
将正确写入变量,但您也需要以线程安全的方式读取变量,因此基本上需要一个锁。请参阅Eric Lippert。如果您只是从其他地方检索您的double
,情况将完全相同。是的,如果您在同一MyClass实例上同时多次调用SomeMethod()
,它可能会损坏。@zerkms,这样它就可以正确损坏了吗?@Sniffer:没错await
没有在此处添加任何新的语义。您提出的问题表明,您正在考虑做一些危险的事情,这可能是架构级别的错误决策造成的。设计应用程序时,不要跨线程共享内存。想象一个线程是一个进程,你不能很容易地共享内存;那么您将如何设计您的应用程序呢?尽管这是一个不同的问题。在这种情况下,解决问题的最佳方法是什么?@Sniffer您希望实现什么?假设有两个并发线程分配,哪一个必须赢?在64位机器上写一个double保证是原子的,不是吗?我记得有人在一次类似的会议上提到过question@zerkms我只想确保原子写入SomeProperty
,因为我知道它可能会被破坏。即使它是原子的,你也需要某种屏障。仅仅更新值可能会导致不同线程的视图不一致。我喜欢您的解释,因为起初我认为SomeProperty
将在一个continuation中设置。谢谢。控件返回不是基于线程,而是基于当前SynchronizationContext。好的,通常控件返回到调用任务的线程。我会合并这个。
public async Task SomeMethod()
{
var temp = await someService.SomeAsyncMethod();
lock (_lock)
{
SomeProperty = temp;
}
}
public async Task SomeMethod()
{
Interlocked.Exchange(ref _backingField, await someService.SomeAsyncMethod());
}