Вчера я продолжил работу над своей программой после месячного перерыва. Я ничего не менял в коде, но теперь мое приложение больше не запускается. В какой-то момент он просто прерывает выполнение и, кажется, застрял в тупике, хотя я не уверен, действительно ли это тупик, поскольку это происходит, когда метод возвращается - в точке, где это обычно не должно происходить.
Я не могу показать вам код, так как он огромный. Но я могу с уверенностью сказать, что единственным действием за его собственный поток является доступ к некоторым элементам пользовательского интерфейса, которые вызываются диспетчером. И до вчерашнего дня все работало нормально, я ничего там не менял.
Это место, где это происходит:
internal override Task InitializeAddIns()
{
try
{
Action action = () => this._addinProvider.InitializeAddins();
Task t = Task.Factory.StartNew(action);
return t;
}
catch (Exception ex)
{
Debugger.Break();
return null;
}
}
Код звонка:
// Initialize AddIns
splash.SplashText = "SplashScreen:step_searchAddIns".Translate();
this._addinSystem.InitializeAddIns();
splash.SplashText = "SplashScreen:step_startAddIns".Translate();
await Task.Run(() => this._addinSystem.RunAddins());
// Resolve libraries with NativeCompressor
splash.SplashText = "SplashScreen:step_resolveDependencies".Translate();
Задача запускается и возвращает 't'. InitializeAddins() -method успешно завершает работу (проверил его с помощью отладчика - журналы также показывают, что он полностью завершается). Следующим шагом является то, что строка объявления "действие" отмечена (по завершении). Затем отладка заканчивается, и больше ничего не происходит. Даже этот вызов диспетчера не вызван:
Dispatcher.CurrentDispatcher.Hooks.DispatcherInactive += (sender, args) => this.Update();
Мое единственное предположение заключается в том, что где-то есть тупик. Я не могу объяснить иначе, почему все исполнение останавливается и застревает. Я просто не могу найти подсказки, с чего начать поиск. Я переработал недавно введенный код и добавил некоторые расширенные методы блокировки, которые также обнаруживают взаимоблокировки. До сих пор не обнаружен тупик.
Поскольку я не знаю, что может вызвать проблему, я попытался использовать WinDbg и SOSEX, чтобы найти источник ошибок. К сожалению, я не запускаю WinDbg. Он проверяет сервер Symbol, а последние выходы следующие:
CLRDLL: невозможно найти mscordacwks_AMD64_x86_4.0.30319.34209.dll по запросу mscorwks CLRDLL: невозможно найти "SOS_AMD64_x86_4.0.30319.34209.dll" на пути Не удается автоматически загрузить SOS CLRDLL: загруженный DLL файл mscordacwks_AMD64_x86_4.0.30319.34209.dll CLR DLL: Загруженная DLL mscordacwks_AMD64_x86_4.0.30319.34209.dll
Хотя это явно что-то загрузило, я получаю это сообщение при вызове команды SOSEX! Dlk:
0: 028>! Dlk Невозможно инициализировать интерфейс данных.NET. Требуется версия 4.0.30319.34209 файла mscordacwks.dll. Найдите и загрузите правильную версию mscordacwks.dll. См. Документацию для команды.cordll. Изучение критических разделов... Не обнаружены блокировки.
Поэтому я действительно не знаю, как устранить эту ошибку. Что может быть причиной такого поведения? Я даже не получаю исключения. Я уже включил исключения CLR, но даже те, кто их бросает. Это довольно странно, я обычно ожидаю, что это блокирование происходит где-то посередине, а не после выхода метода...
Я нашел источник этой проблемы. Это был мой Splashscreen, простое окно, доступ к которому осуществляется этими методами, чтобы обновить текущий статус (который добавляется AddIn и т.д.). Это было абсолютно не потокобезопасно (интересно, почему это сработало раньше...).
Я изменил его на следующий код во всех свойствах. Было бы неплохо, если бы можно было проверить этот код и подтвердить, что он не взломан или плохой подход, так как он выглядит немного сложнее... Но он работает до сих пор.
public string SplashText
{
get
{
using (ThreadLock.Lock(_lock))
{
return _splashText;
}
}
set
{
if (_dispatcher.CheckAccess())
{
_splashText = value;
OnPropertyChanged();
return;
}
_dispatcher.Invoke(() =>
{
_splashText = value;
OnPropertyChanged();
});
}
}
ThreadLock
? Почему вы используете это вместо оператора lock
C #? Что касается безопасности кода, я всегда немного нервничаю, когда вижу диспетчер Invoke
и блокировку синхронизации потоков одновременно. Синхронный Invoke
сам по себе эквивалентен блокировке, так что это возможность тупика. Но до тех пор , как вы осторожны , чтобы только когда - нибудь , что замок внутри диспетчера Invoke
вызова (т.е. всегда принимают замки в том же порядке), то это будет хорошо. Просто иногда бывает сложно сделать такую гарантию.
Есть 4 предусловия, которые необходимо выполнить для возникновения тупика. Если один из них отсутствует, тупика не будет. Эти предварительные условия:
Последнюю можно также назвать "Сроки". Поскольку это зависит от того, как Windows назначает процессорное время, вы можете жить без взаимоблокировок в течение многих лет. Скорее всего, это относится к многоядерным процессорам, потому что круговое ожидание легче достичь, если два потока действительно выполняются параллельно.
mscordacwks_AMD64_x86_4.0.30319.34209.dll - файл, который не существует. Пожалуйста, признайтесь, что вы переименовали другой файл в это имя, потому что вы видели его WinDbg.
Имя указывает, что вы пытаетесь отладить 32-разрядное приложение с 64-разрядным отладчиком. Microsoft не поддерживает это. Вы можете отлаживать 64-разрядные.NET-приложения в 64-битных WinDbg и 32-разрядных.NET-приложениях в 32-битном WinDbg (который также работает на 64-битной ОС BTW).
Если вы имеете только 64-битный файл дампа и не можете воспроизвести проблему, вам не повезло. Нет никакого способа (я несколько раз исследовал), чтобы он работал, и нет никакого способа конвертировать дамп с 64 бит до 32 бит.
Кроме того, ваш подход к использованию SOSEX ' !dlk
Dlk хорош. Он должен обнаруживать взаимоблокировки, вызванные операторами lock
С#.
Я не согласен, чтобы код выполнялся синхронно, как было предложено в ответ Якоба Кристенсена. Хотя вы можете сделать это в небольшом приложении, это потребует слишком большого перезаписи в более крупном приложении.
Переход к синхронному выполнению и обратно к асинхронному может снова привести к необнаруженной ситуации (время может измениться, и это стало менее вероятным для возникновения взаимоблокировки).
Имхо лучше понять тупик (который нуждается в понимании внутренних Windows, поэтому вы можете прочитать книгу). Когда вы понимаете Windows Threading, вы также лучше понимаете async
и await
.
Затем я соглашаюсь с Питером Дунихо, который сказал:
Если вы только когда-либо обращаетесь к полю _splashText в потоке GUI, то есть в коде, вызываемом непосредственно WPF в этом потоке, или в коде, который вы явно отправили на этот поток, - тогда да... вам не нужны никакие другие так как все обращения этого поля будут происходить синхронно в этом единственном потоке.
Обратите внимание, что существует не только "поток GUI". Я вижу, что все больше разработчиков создают несколько потоков пользовательского интерфейса, то есть потоки, которые имеют собственную очередь сообщений. Надеюсь, у вас есть только один.
Первый шаг - попытаться запустить код синхронно без каких-либо задач.
Второй шаг - проверить, ожидаете ли вы правильно. Например, вам не хватает this._addinSystem.InitializeAddIns()
при вызове this._addinSystem.InitializeAddIns()
. Это означает, что вызов InitializeAddIns
может не завершиться до вызова RunAddIns
. Вы должны добавить это:
await this._addinSystem.InitializeAddIns();
Наконец, вы можете не дождаться правильного вызова вызывающего кода. Если, например, вы void
возврата функции возврата, вызов может быть заблокирован:
// This may deadlock because you are awaiting a void returning function!
await MyMethod();
//...
public void MyMethod()
{
await this._addinSystem.InitializeAddIns();
await Task.Run(() => this._addinSystem.RunAddins());
}
_addinProvider.InitializeAddins()
?