这与Is it safe to signal and immediately close a ManualResetEvent?密切相关,并且可能为该问题提供一种解决方案。
假设我有一堆可能要执行相同工作的线程,但只应允许一个线程执行,而其他线程则应等到工作人员完成并使用其结果。
所以基本上我只希望工作完成一次。
更新:我要补充一点,这不是可以使用.net 4的Lazy 解决的初始化问题。一次我的意思是每个任务一次,这些任务是在运行时确定的。从下面的简化示例中可能不清楚。
我修改了Hans Passant对上述问题的回答中的简单示例,我认为以下内容是安全的。 (它与刚才描述的用例略有不同,但是就线程及其关系而言,是等效的)
static void Main(string[] args)
{
ManualResetEvent flag = new ManualResetEvent(false);
object workResult = null;
for (int ix = 0; ix < 10; ++ix)
{
ThreadPool.QueueUserWorkItem(s =>
{
try
{
flag.WaitOne();
Console.WriteLine("Work Item Executed: {0}", workResult);
}
catch (ObjectDisposedException)
{
Console.WriteLine("Finished before WaitOne: {0}", workResult);
}
});
}
Thread.Sleep(1000);
workResult = "asdf";
flag.Set();
flag.Close();
Console.WriteLine("Finished");
}
我想我的问题的核心是:
就内存障碍而言,是否因ObjectDisposedException而中止对WaitOne的调用,是否等效于成功对WaitOne的调用?
那应该确保其他线程安全地访问变量workResult。
我的猜测:这必须是安全的,否则WaitOne如何安全地确定ManualResetEvent对象首先已关闭?
最佳答案
这是我看到的:
ObjectDisposedException
,因为您的代码具有以下竞争条件:可以在所有线程都成功调用flag.waitOne之前调用
如何处理取决于在flag.waitOne之后执行代码的重要性。
这是一种方法:
如果确实已经启动了所有线程,则可以在调用flag.close之前进行一些额外的同步。您可以通过在
StartNew
上使用 Task.Factory
而不是Thread.QueueUserWorkItem
来实现。可以等待任务完成,然后调用flag.close,从而消除了竞争条件和处理ObjectDisposedException
的需要您的代码将变为:
static void Main(string[] args)
{
ManualResetEvent flag = new ManualResetEvent(false);
object workResult = null;
Task[] myTasks = new Task[10];
for (int ix = 0; ix < myTasks.Length; ++ix)
{
myTasks[ix] = Task.Factory.StartNew(() =>
{
flag.WaitOne();
Console.WriteLine("Work Item Executed: {0}", workResult);
});
}
Thread.Sleep(1000);
workResult = "asdf";
flag.Set();
Task.WaitAll(); // Eliminates race condition
flag.Close();
Console.WriteLine("Finished");
}
正如您在上面看到的,任务允许额外的同步,这将消除您所看到的竞争状况。
需要特别注意的是,ManualResetEvent.waitOne执行内存屏障,因此workresult变量将是最新的变量,而无需任何其他的内存屏障或 volatile 读取。
因此,要回答您的问题,如果您必须避免额外的同步并通过使用方法来处理ObjectDisposed异常,我会认为已处置的对象并未为您执行内存障碍,则您必须在catch块中调用
Thread.MemoryBarrier
以确保已读取最新值。但是异常的代价很高,如果您可以避免在正常的程序执行中使用它们,我相信这样做是明智的。
祝你好运!