C # - Решение потенциального тупика - PullRequest
0 голосов
/ 01 ноября 2018

У меня есть 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. Кто-то вызывает Begin из потока, не являющегося пользовательским интерфейсом.
  2. Входит в замок
  3. Кто-то вызывает Begin или End из потока пользовательского интерфейса и блокируется
  4. При первом вызове Begin выполняется попытка отправки в поток пользовательского интерфейса.

Результат: тупик!

Я хочу сделать этот поток-помощник безопасным, чтобы любой поток мог вызывать Begin или End в любой момент времени, заинтересованный в том, чтобы узнать, существует ли какой-либо известный шаблон, какие-либо идеи?

Спасибо!

Ответы [ 3 ]

0 голосов
/ 01 ноября 2018

Вся цель создания «потока пользовательского интерфейса» - избежать такой синхронизации. Тот факт, что весь код пользовательского интерфейса должен выполняться в одном потоке, означает, что он, по определению, не может работать одновременно. Вам не нужно использовать блокировки, чтобы код UI выполнялся атомарно , потому что он все работает в одном потоке .

Написание кода пользовательского интерфейса, который требовал от программиста собственной блокировки, достаточно сложен и подвержен ошибкам, так что вся структура была разработана вокруг идеи, что неразумно ожидать, что люди это сделают (правильно), и что гораздо проще просто заставить весь код пользовательского интерфейса идти в один поток, где другие механизмы синхронизации не понадобятся.

0 голосов
/ 01 ноября 2018

Вот код "без блокировки": Я переместил диспетчеризацию в поток пользовательского интерфейса за пределы блокировки.

(Может ли кто-нибудь еще увидеть здесь потенциальный тупик?)

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);
            });
        }
    }
}
0 голосов
/ 01 ноября 2018

Не блокируйте для всего метода. Блокировка только тогда, когда вы касаетесь полей, которые нуждаются в этом, и разблокировать, как только вы закончите. Блокировка и разблокировка каждый раз, когда вы касаетесь этих полей. В противном случае вы получите такие тупики.

Вы также можете рассмотреть возможность использования ReaderWriterLockSlim, который различает блокировки чтения и записи. Он позволяет читать несколько потоков одновременно, но блокирует всех, когда блокировка записи выполняется. В этой документации есть пример того, как его использовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...