C#-解决潜在的死锁解决方案

时间:2018-11-01 12:52:04

标签: c# wpf deadlock

我有一个LongOperationHelper,可以在每个可能很长的操作上激活它。 它显示一个半透明的图层,直到操作结束和旋转控件指示进度时才允许单击。

看起来像这样(缺少一些业务逻辑,但我认为这个想法很明确):

已编辑:(添加了实际上需要锁定的常见状态的缺少代码-这更像是有问题的代码)

(我的解决方案已张贴在答案栏中)

public static class LongOperationHelper
{
    private static object _synchObject = new object();
    private static Dictionary<string, int> _calls = new Dictionary<string, int>();

    private static Action<string> DisplayLongOperationRequested;
    private static Action<string> StopLongOperationRequested;

    public static void Begin(string messageKey)
    {
        lock (_synchObject)
        {
            if (_calls.ContainsKey(messageKey))
            {
                _calls[messageKey]++;
            }
            else
            {
                _calls.Add(messageKey, 1);

                DispatcherHelper.InvokeIfNecesary(() =>
                {
                    //Raise event for the MainViewModel to display the long operation layer
                    DisplayLongOperationRequested?.Invoke(messageKey);
                });
            }
        }
    }

    public static void End(string messageKey)
    {
        lock (_synchObject)
        {
            if (_calls.ContainsKey(messageKey))
            {
                if (_calls[messageKey] > 1)
                {
                    _calls[messageKey]--;
                }
                else
                {
                    _calls.Remove(messageKey);

                    DispatcherHelper.InvokeIfNecesary(() =>
                    {
                        //Raise event for the MainViewModel to stop displaying the long operation layer
                        StopLongOperationRequested?.Invoke(messageKey);
                    });
                }
            }
            else
            {
                throw new Exception("Cannot End long operation that has not began");
            }
        }
    }
}

如您所见,如果存在以下情况,则可能存在死锁:

  1. 有人调用是从非UI线程开始的。
  2. 它进入锁子
  3. 有人从UI线程调用Begin或End并被锁定
  4. 第一个Begin调用尝试调度到UI线程。

结果:死锁!

我想使此Helper线程安全,以便任何线程都可以在任何给定时间调用Begin或End,以查看是否有任何已知的模式,思路?

谢谢!

3 个答案:

答案 0 :(得分:1)

不要锁定整个方法。仅在您触摸需要的字段时锁定,并在完成后立即解锁。每次触摸这些字段时都进行锁定和解锁。否则,您将最终陷入这种僵局。

您还可以考虑使用ReaderWriterLockSlim,它可以区分读锁和写锁。它允许多个线程同时读取,但是在进行写锁定时将所有人锁定。在该文档中有一个有关如何使用它的示例。

答案 1 :(得分:0)

拥有“ UI线程”的全部目的是避免完全像这样进行同步。所有UI代码都需要在单个线程上运行的事实意味着,根据定义,它不能同时运行。您不必使用锁来使您的UI代码自动运行,因为它们都在单个线程上运行

编写要求程序员自行锁定的UI代码非常困难且容易出错,因此整个框架的设计都是基于这样的想法:期望人们(正确地)这样做是不合理的,并且简单起来要容易得多强制所有UI代码进入单个线程,而无需其他同步机制。

答案 2 :(得分:0)

这是“无死锁”代码: 我已经将对UI线程的调度重新定位到了锁的外部。

(有人还能在这里看到潜在的僵局吗?)

public static class LongOperationHelper
{
    private static object _synchObject = new object();
    private static Dictionary<string, int> _calls = new Dictionary<string, int>();

    private static Action<string> DisplayLongOperationRequested;
    private static Action<string> StopLongOperationRequested;

    public static void Begin(string messageKey)
    {
        bool isRaiseEvent = false;

        lock (_synchObject)
        {
            if (_calls.ContainsKey(messageKey))
            {
                _calls[messageKey]++;
            }
            else
            {
                _calls.Add(messageKey, 1);

                isRaiseEvent = true;
            }
        }

        //This code got out of the lock, therefore cannot create a deadlock
        if (isRaiseEvent)
        {
            DispatcherHelper.InvokeIfNecesary(() =>
            {
                //Raise event for the MainViewModel to display the long operation layer
                DisplayLongOperationRequested?.Invoke(messageKey);
            });
        }
    }

    public static void End(string messageKey)
    {
        bool isRaiseEvent = false;

        lock (_synchObject)
        {
            if (_calls.ContainsKey(messageKey))
            {
                if (_calls[messageKey] > 1)
                {
                    _calls[messageKey]--;
                }
                else
                {
                    _calls.Remove(messageKey);

                    isRaiseEvent = true;
                }
            }
            else
            {
                throw new Exception("Cannot End long operation that has not began");
            }
        }

        //This code got out of the lock, therefore cannot create a deadlock
        if (isRaiseEvent)
        {
            DispatcherHelper.InvokeIfNecesary(() =>
            {
                StopLongOperationRequested?.Invoke(messageKey);
            });
        }
    }
}