这与Is it safe to signal and immediately close a ManualResetEvent?密切相关,可能会为该问题提供一种解决方案。
假设我有一堆线程可能想要做同样的工作,但只允许一个人做这件事,其他人应该等到工人完成并使用它的结果。
所以我基本上只希望工作一次。
更新: 让我补充一点,这不是可以使用.net 4的Lazy< T>解决的初始化问题。我的意思是每个任务一次,这些任务是在运行时确定的。从下面的简化示例中可能不太清楚这一点。
修改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");
}
我想我的问题的核心是:
对WaitOne的调用是否因ObjectDisposedException而中止,相当于在内存障碍方面成功调用WaitOne?
这应确保其他线程安全访问变量 workResult 。
我的猜测:它必须是安全的,否则WaitOne怎么能安全地发现ManualResetEvent对象首先被关闭了?
答案 0 :(得分:3)
以下是我看到的内容:
ObjectDisposedException
,因为您的代码显示以下竞争条件:
如何处理这取决于在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变量将是最新更新的,不需要任何进一步的内存屏障或易失性读取。
所以要回答你的问题,如果你真的必须避免额外的同步并通过你的方法处理一个ObjectDisposed异常,我会争辩说,一个被处置的对象没有为你执行内存屏障,您必须在catch块中调用Thread.MemoryBarrier
以确保已读取最新值。
但是异常是昂贵的,如果你可以避免它们用于正常的程序执行,我相信这样做是明智的。
祝你好运!答案 1 :(得分:0)
几点:
如果这是.NET 4,那么Lazy是更好的方法。
从内存障碍的角度来看,它是否相同是无关紧要的 - 例外不应该是正常代码路径的一部分。我认为行为是未定义的,因为这不是预期的用例。
答案 2 :(得分:0)
对于我必须解决的实际问题反映了一点,这比简化示例复杂一点,我决定使用Monitor.Wait和Monitor.PulseAll。
Joe Albahari的 Cread中的线程证明非常有用,在这种特殊情况下,以下部分适用:http://www.albahari.com/threading/part4.aspx#_Signaling_with_Wait_and_Pulse