在ManualResetEvent.WaitOne()上捕获ObjectDisposedException是否安全

本文关键字:ObjectDisposedException 是否 安全 ManualResetEvent WaitOne | 更新日期: 2023-09-27 18:24:59

这与发出信号并立即关闭手动重置事件是否安全密切相关?并且可能为该问题提供一种解决方案。

假设我有一堆线程可能想做同样的工作,但应该只允许一个线程做,其他线程应该等到工作者完成并使用其结果。

所以基本上我希望工作只做一次。

更新: 让我补充一点,这不是一个初始化问题,可以使用.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");
}

我想我问题的核心是:

就内存障碍而言,由于ObjectDisposedException而中止的对WaitOne的调用是否相当于对WaitOne的成功调用

这应该确保其他线程安全访问变量workResult

我的猜测是:它必须是安全的,否则WaitOne怎么能安全地发现ManualResetEvent对象一开始就被关闭了?

在ManualResetEvent.WaitOne()上捕获ObjectDisposedException是否安全

以下是我看到的:

  • 您得到ObjectDisposedException是因为您的代码显示以下竞赛条件:
    • 在所有线程都成功调用flag.waitOne之前,可以调用flag.close

如何处理这一问题取决于flag.waitOne.之后代码的执行有多重要

这里有一种方法:

如果所有已经启动的线程都应该执行,那么在调用flag.close之前,您可以进行一些额外的同步。您可以通过在Task.Factory上使用StartNew而不是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执行内存屏障,因此工作结果变量将是最新更新的变量,而不需要任何进一步的内存屏障或易失性读取。

因此,为了回答您的问题,如果您确实必须避免额外的同步,并通过使用您的方法来处理ObjectDisposed异常,我认为已处理的对象没有为您执行内存屏障,那么您必须在捕获块中调用Thread.MemoryBarrier,以确保已读取最新的值。

但是异常是昂贵的,如果可以在正常程序执行中避免它们,我相信这样做是谨慎的。

祝你好运!

几点:

  1. 如果这是.NET 4,那么Lazy是更好的方法。

  2. 从内存屏障的角度来看,它是否等效是无关紧要的——异常永远不应该是正常代码路径的一部分。我认为行为是未定义的,因为这不是预期的用例。

考虑到我必须解决的实际问题,这比简化的例子要复杂一些,我决定使用Monitor.Wait和Monitor.PulseAll.

Joe Albahari的C#线程被证明非常有用,在这种特殊情况下,以下部分适用:http://www.albahari.com/threading/part4.aspx#_Signaling_with_Wait_and_Pulse

相关文章:
  • 没有找到相关文章