【问题标题】:Lock on static object from within a delegate is not working从委托中锁定静态对象不起作用
【发布时间】:2012-03-27 16:02:53
【问题描述】:

这怎么行不通?

private static object Lock_HandleError = new object();
public static void HandleError(Exception ex)
{
    lock( Lock_HandleError )
    {
        //IF the UI is processing a visual tree event (such as IsVisibleChanged), it throws an exception when showing a MessageBox as described here: http://social.msdn.microsoft.com/forums/en-US/wpf/thread/44962927-006e-4629-9aa3-100357861442
        //The solution is to dispatch and queue the MessageBox. We must use BeginInvoke because dispatcher processing is suspended in such cases.
        Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate()
        {
            lock( Lock_HandleError )
            {
                Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate(){ 
                    HandleError(new Exception("testing purposes only")); 
                }, DispatcherPriority.Background);

                MessageBox.Show(ex.Message, "Application Error", MessageBoxButton.OK, MessageBoxImage.Error);
                //This point is not reached until the user clicks "OK"
            }
        }, DispatcherPriority.Background);
    }
}

public void main()
{
    HandleError(new Exception("The first error"));
}

上述代码的预期行为是一次出现一条错误消息,当用户单击“确定”时,Lock_HandleError 对象上的HandleError 可以继续 - 但我得到的是无限级联的错误消息,而从未点击“OK”。

为什么这个锁不起作用?

通过在每个 lock 语句的入口和出口处设置断点,我可以清楚地看到委托正在调用 lock() 并再次调度对“HandleError”的新调用,然后在 MessageBox 上暂停以等待用户输入。

同时,在另一个线程中,对 HandleError 的分派调用开始运行 - 但不是像应有的那样等待 lock() 语句,它只是通过它爆炸,即使 MessageBox 委托明确放置了一个锁并且尚未释放它。

【问题讨论】:

  • 我在多线程方面还不够,但我想知道您是否可以尝试在BeginInvoke 方法中删除lock
  • 移除锁不会完成任何事情 - 因为锁的行为就像它甚至不存在一样。当 UI 线程等待用户在消息框上单击“确定”时,它会处理来自应用程序其他区域的对 HandleError() 的额外调用,我需要它停止并等待。
  • 我尝试在这里使用信号量解决问题:stackoverflow.com/questions/9894750/…

标签: c# .net-4.0


【解决方案1】:

两部分答案:

  1. 了解锁是可重入的。当一个线程已经持有一个对象的锁时,该线程可以一次又一次地获取相同的锁而不会阻塞。

  2. 当第一个 MessageBox 启动时,UI 线程仍在发送消息,因此对 HandleError 的后续(递归)调用正在 UI 线程上进行处理(因为它已经持有锁,所以可以重新- 输入它)。

【讨论】:

  • 到目前为止,这是最完整的解释,因为我都希望 MessageBox 阻塞它正在运行的线程(根据2. 它没有),我期待锁阻塞就像信号量一样,无论哪个线程拥有它(根据1. 它没有)。虽然我的问题并没有明确要求解决我的问题,但只是为什么我的问题不起作用,但我仍然想知道是否有解决此类问题的简单方法。
【解决方案2】:

为什么这个锁不起作用?

允许线程进入它已经拥有的锁语句。本质上,锁不会阻塞自己的线程。

因此,发生的事情是原始线程获得了锁,然后被允许将消息添加到调度程序的队列中。它可以根据需要添加任意数量。

Dispatcher 在处理时获取第一条消息,然后调用 HandleError。由于这是在dispatcher线程中运行的,因此允许进入外锁和内锁,并再次调用HandleError,以无限循环的方式将新消息递归添加到队列中。

【讨论】:

  • 感谢您的解释。显然 lock() 不是我想要使用的。我怎样才能达到我的预期?
  • @Alain 为什么要用 BeginInvoke 递归调用自己?这将是有问题的,因为任何给你带来你所追求的行为的东西都会有效地锁定你。
  • 这是为了模拟实践中发生的情况,即许多不同的线程同时遇到错误,并且它们都在 UI 线程上调用HandleError()——而不是等待一个 MessageBox 被关闭在处理下一个错误之前,应用程序会同时级联所有错误。如果我们有一个反复出现的错误,这个锁不起作用会阻止我检测和处理无限级联的消息框。
  • @Alain 如果您从内部锁中取出对 HandleError 的调用(您的 BeginInvoke 再次调用 HandleError),那么它将正常工作,没有任何锁。问题是递归发布到调度员......
  • @Alain 您可以将锁定切换为使用 Semaphore(或 .NET 4 中的 SemaphoreSlim) - 这将避免您遇到的问题:msdn.microsoft.com/en-us/library/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多