У меня есть 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");
}
}
}
}
Итак, как вы, вероятно, видите, здесь есть потенциальный тупик, если:
Результат: тупик!
Я хочу сделать этот помощник потокобезопасным, чтобы любой поток мог вызывать Begin или End в любой момент времени, чтобы узнать, есть ли какой-либо известный шаблон, какие-либо идеи?
Спасибо!
Я не уверен, зачем вам вообще нужен замок.
@Eibi, он не будет работать должным образом, пока вы не Удалить весь этот код. Разместите код действительный, если хотите, чтобы люди оказывали конкретную помощь. В противном случае все, что можно сделать, это опубликовать общий код. Этот Helperпричины проблема и не нужен
@Eibi: делегаты, которые вы передаете в Dispatcher.Invoke, будут помещены в очередь и выполнены последовательно в одном и том же потоке диспетчера, так почему вы вообще используете блокировку?
Мне нужна блокировка, потому что, как я уже упоминал, внутри блокировки есть логика, которая изменяет другие поля, такие как списки и другие локальные состояния, которые необходимо защитить от чтения и записи.
Я изменю код, чтобы показать необходимость синхронизации потоков.
@Eibi, вам не нужна блокировка, при необходимости вы можете использовать одну из параллельных коллекций. Или вы можете изменить свой код так, чтобы он не изменял глобальный список. Он может генерировать нужные данные локально и возвращать их из фоновой задачи. Вы никогда, никогда все равно используете блокировку, когда используете диспетчер - вы пытаетесь вызвать что-то в том же потоке, который вы только что заблокировали. В любом случае вы не показали ничего, что работает в фоновом режиме.
@Eibi, вы показали, как пытались решить какую-то проблему, но не объяснили, в чем проблема. Опубликованный вами код ничего не решает. Делать у вас фоновая ветка или задача? Как начать? Должен ли он производить продукцию? Доступ к глобальному состоянию? Зачем это нужно?
Предложение: неразумно оставлять текущий метод внутри блокировки. Вы должны удерживать блокировку как можно короче и не должны вызывать никаких других методов. Вы определенно не перескакиваете по потокам, вызывая Dispatcher.
Спасибо @Michael Puckett II, полностью согласен! поэтому я взял код диспетчера пользовательского интерфейса из-под блокировки и оставил его минимальным.





Не блокируйте весь метод. Блокируйте только тогда, когда вы касаетесь нужных полей, и разблокируйте, как только закончите. Блокируйте и разблокируйте каждый раз, когда вы касаетесь этих полей. В противном случае вы получите такие тупиковые ситуации.
Вы также можете рассмотреть возможность использования ReaderWriterLockSlim, который различает блокировки чтения и блокировки записи. Он позволяет нескольким потокам читать одновременно, но блокирует всех при блокировке записи. В этой документации есть пример того, как его использовать.
Вся цель наличия «потока пользовательского интерфейса» - избежать такой синхронизации. Тот факт, что весь код пользовательского интерфейса должен выполняться в одном потоке, означает, что он по определению не может выполняться одновременно. Вам не нужно использовать блокировки, чтобы ваш код пользовательского интерфейса выполнялся атомарно потому что все это работает в одном потоке.
Написание кода пользовательского интерфейса, который требовал, чтобы программист выполнял свою собственную блокировку, достаточно сложен и подвержен ошибкам, поэтому весь фреймворк был спроектирован вокруг идеи, что неразумно ожидать, что люди будут делать это (правильно), и что гораздо проще просто принудительно заставить весь пользовательский интерфейс код для перехода в один поток, где другие механизмы синхронизации не понадобятся.
Вызовы этого помощника не «все выполняются в одном потоке», как вы говорите, их также можно вызывать из фонового потока. следовательно, необходима синхронизация.
@Eibi Неважно, откуда идут звонки. Код ничего не делает, кроме планирования выполнения кода в потоке пользовательского интерфейса, который является один поток. Итак, весь значимый код является работает в одном потоке.
Если под «осмысленным кодом» вы ссылаетесь на код, который зарегистрирован для этих событий и выполняет фактическое отображение (привязка WPF в этом случае), чем я согласен, он выполняется в потоке пользовательского интерфейса (который является одиночным :)). но как инфраструктура этот помощник должен быть потокобезопасным.
@Eibi Преобразование вашего вопроса в совершенно другой вопрос неуместно.
Я определенно не знал. Я согласен, что в нем не хватало некоторых деталей, поэтому редактирование :). Я просто добавил причину блокировки в виде некоторых внутренних состояний (в данном случае Dictionary). Для меня это все тот же вопрос.
@Eibi Полное изменение кода для синхронизации радикально меняет вопрос, как его синхронизировать. Это все равно что сказать: «как мне приготовить по этому рецепту?» тот же вопрос, если вы измените рецепт на совершенно другой рецепт. Задаю один и тот же вопрос о двух совершенно разных фрагментах кода это разные вопросы.
Честно говоря, при всем уважении, иногда действительно не понимаю некоторых негативных реакций. Я делаю все возможное, чтобы поделиться опытом и знаниями (в этом-то и идея, не так ли?). Я сделал все, чтобы вопрос и ответ были максимально ясными (пропустил первый раз, потом отредактировал и исправил). Судя по вашему ответу, вы упустили мою точку зрения в этом вопросе. Разве вы не видите, как здесь прилагаются усилия по обмену мыслями? К сожалению, такая реакция заставляет меня полностью удалить вопрос ...
@Eibi Я точно ответил на ваш вопрос. То, что вы намеревались задать другой вопрос радикально, не отменяет того факта, что вы не задавали этот вопрос. Ваш вопрос не был не понятно, ответ просто бесполезен для вас, потому что ваша реальная проблема сильно отличалась от той, о которой вы спрашивали. Преобразование вашего вопроса в совершенно другой вопрос - не лучший ответ на это.
Извините, но я бы знал, если бы вы ответили на мой вопрос. Я знаю, о чем просил, до и после изменения. Эта часть не была изменена. Оставался вопрос: как обеспечить потокобезопасность, чтобы избежать тупика? Это было раньше и сейчас. Как ты думаешь, ты мне помог ?! Я попросил помощи, чтобы найти способ обеспечить безопасность потоков и избежать взаимоблокировок.
@Eibi Ответ на ваш первоначальный вопрос заключался в том, чтобы снять блокировки. Они не нужны для правильной синхронизации выполняемой вами работы, и их удаление устраняет тупик. Это было решение твоей проблемы. Затем вы изменили задачу, что изменило ответ. Ответ на ваш исходный вопрос бесполезен для вас, потому что ваш вопрос не отражает вашу проблему, а не потому, что ответ на нее не дает.
Вот код "без тупиков": Я переместил отправку в поток пользовательского интерфейса за пределы блокировки.
(Может ли кто-то все еще видеть здесь потенциальный тупик?)
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);
});
}
}
}
Используйте
async/awaitвместо блокировок и Invoke. Они тебе не нужны. Опубликуйте код, который вы используете, вBegin,Endили как вы сообщаете о прогрессе. Должно быть легко вызывать все, что занимает много времени, с помощьюawait Task.Run(whatever);и обновлять пользовательский интерфейс до / после оператораawait. Вы можете использоватьIProgress<T>для сообщения о прогрессе из другого потока, не требуя вызова диспетчера.