【问题标题】:WaitHandle is closed when it shouldn't haveWaitHandle 在不应该关闭时关闭
【发布时间】:2013-01-02 05:59:12
【问题描述】:

这段代码大部分时间都有效,所以我在考虑一些竞争条件。 Result 类是不可变的,但我认为问题不在于该类。

public Result GetResult()
{
    using (var waitHandle = new ManualResetEvent(false))
    {
        Result result = null;

        var completedHandler = new WorkCompletedEventHandler((o, e) =>
        {
            result = e.Result;

            // somehow waitHandle is closed, thus exception occurs here
            waitHandle.Set();
        });
        try
        {
            this.worker.Completed += completedHandler;

            // starts working on separate thread
            // when done, this.worker invokes its Completed event
            this.worker.RunWork();

            waitHandle.WaitOne();

            return new WorkResult(result);
        }
        finally
        {
            this.worker.Completed -= completedHandler;
        }
    }
}

编辑:抱歉,我在调用 GetResult() 方法之前错过了对 this.worker.RunWork() 的调用。这显然导致(有时)两次执行相同的工作,尽管我不确定为什么 waitHandle 在 waitHandle.Set() 之前关闭,尽管 Completed 事件触发了两次。这根本没有影响 IO 工作(结果是正确的;在我更改代码以手动关闭 waitHandle 之后)。

因此,Iridium's answer 应该是最接近的答案(如果不是正确的答案),即使问题不完整。

【问题讨论】:

  • 由于您的 using 子句,waithandle 已经被调用。如果你在完成的事件处理程序中处理等待句柄,你应该没问题。
  • 不,WaitHandle,在大多数情况下,在工作完成后被释放(参见 waitHandle.WaitOne() after this. worker.RunWork())
  • 那么在这种情况下,您必须等到其他人给出答案。
  • ReSharper 允许访问已处理的关闭错误
  • 参见 waitHandle.WaitOne() - waitHandle 不应该被释放(它应该在 waitHandle 发出信号后被释放)。

标签: c# multithreading waithandle


【解决方案1】:

在您提供的代码中似乎没有什么特别的问题,这表明您未显示的代码中可能存在导致问题的东西。我假设您正在使用的worker 是您的代码库的一部分(而不是像BackgroundWorker 这样的.NET BCL 的一部分?)如果出现问题,可能值得发布代码这就是问题所在。

例如,如果同一个工作线程被多个线程重复使用(或者有一个错误,其中Completed 可以为同一个工作多次引发),那么如果工作线程使用“通常”意味着用于调用事件处理程序,即:

var handler = Completed;
if (handler != null)
{
    handler(...);
}

您可能有一个实例,其中var handler = Completed;finally 子句之前执行(因此在completedHandlerCompleted 事件分离之前),但handler(...)在退出using(...) 块之后调用(因此在ManualResetEvent 被释放之后)。您的事件处理程序将在 waitHandle 被释放后执行,并且您看到的异常将被抛出。

【讨论】:

    【解决方案2】:

    没有明显的原因导致发布的代码会失败。但是我们看不到堆栈跟踪,也看不到触发 Completed 事件的逻辑,因此几乎没有机会为您调试它。随意地,如果事件触发不止一次,那么您肯定会遇到这种种族问题。

    棘手的线程问题很难调试,线程竞争是发生在微秒级的问题。尝试调试它就足以让比赛消失。或者这种情况很少发生,以至于有任何发现问题的希望都太少了,以至于无法证明尝试的合理性。

    此类问题通常需要日志记录来诊断比赛。请务必选择轻量级的日志记录方法,日志记录本身可以改变足够的时间以防止比赛发生。

    最后但同样重要的是:请注意,在这里使用线程是没有意义的。通过直接调用由 RunWork() 启动的任何线程执行的代码,您将获得完全相同相同的结果。减去开销和头痛。

    【讨论】:

    • 查看此评论 - GUI 有潜在的进度更新,因此实际上需要单独的线程。 stackoverflow.com/questions/14103360/…
    • 好吧,如果 UI 线程死了,卡在 WaitOne() 调用上,那又有什么意义呢。是时候开始研究 BackgroundWorker 类了,它旨在让您摆脱这样的麻烦。
    • 这不是 UI 线程,当然,它是后台线程,它完成了繁重的工作(worker 只是其中的一小部分)。编辑:我实际上使用了 BackgroundWorker,但只用了很短的时间——我需要一些不同的东西,而且更复杂一些。
    • 如果它已经是一个调用 WaitOne() 的工作线程,那么启动另一个工作线程尤其没有意义。一个不做任何工作的工作线程是没有用的。
    【解决方案3】:

    如果您摆脱了使用您的代码将不会在您指定的行抛出异常... 如果你真的需要,你必须找到一个合适的地方来处理它。

    public Result GetResult()
    {
        var waitHandle = new ManualResetEvent(false);
    
            Result result = null;
    
            var completedHandler = new WorkCompletedEventHandler((o, e) =>
            {
                result = e.Result;
    
                // somehow waitHandle is closed, thus exception occurs here
                waitHandle.Set();
                waitHandle.Dispose();
            });
            try
            {
                this.worker.Completed += completedHandler;
    
                // starts working on separate thread
                // when done, this.worker invokes its Completed event
                this.worker.RunWork();
    
                waitHandle.WaitOne();
    
                return new WorkResult(result);
            }
            finally
            {
                this.worker.Completed -= completedHandler;
            }
    }
    

    【讨论】:

    • 好吧,更麻烦的是,当 waitHandle.Set() 抛出异常时,另一个线程创建了 waitHandle 仍在等待 waitHandle.waitOne(),因此到那时 using 子句无法处理 waitHandle。
    • 您是否费心尝试过这个,或者您只是理论上的推理?
    • 是的,我知道,它会移除异常,但是在创建 waitHandle 之后抛出的任何异常都会阻止运行线程,这将导致处理程序不调用,这将导致未处理的 WaitHandle。
    • 啊,那是你真正的问题。也许等待句柄应该是 RunWork 中的一个参数,以便实现可以发出“完成”的信号?
    • 工作完成由 Completed 事件发出信号。
    猜你喜欢
    • 1970-01-01
    • 2020-11-28
    • 2013-08-17
    • 1970-01-01
    • 1970-01-01
    • 2019-04-24
    • 2021-01-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多