Исполнение останавливается в возможном тупике

1

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

Я не могу показать вам код, так как он огромный. Но я могу с уверенностью сказать, что единственным действием за его собственный поток является доступ к некоторым элементам пользовательского интерфейса, которые вызываются диспетчером. И до вчерашнего дня все работало нормально, я ничего там не менял.

Это место, где это происходит:

    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, но даже те, кто их бросает. Это довольно странно, я обычно ожидаю, что это блокирование происходит где-то посередине, а не после выхода метода...

  • 0
    Можете ли вы показать нам, что происходит внутри _addinProvider.InitializeAddins() ?
  • 0
    Это немного много кода, всего около 1600 строк. Он считывает ресурс всех сборок в каталоге Addins, расширяет пользовательский интерфейс, анализируя этот файл, создает экземпляр AddIn и возвращает после того, как все загружены. Я опубликую код, если на самом деле нет решения, но каковы источники этого поведения в целом, независимо от того, какой код выполняется? Я имею в виду, что этот метод работает без сбоев. После этого он останавливается. Этого не произойдет, если я выполню его синхронно без Задачи, как я только что заметил. Но почему он не должен работать асинхронно? Тупик?
Показать ещё 5 комментариев
Теги:
deadlock
windbg
sosex

3 ответа

1
Лучший ответ

Я нашел источник этой проблемы. Это был мой 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();
        });
    }
}
  • 1
    Какой тип ThreadLock ? Почему вы используете это вместо оператора lock C #? Что касается безопасности кода, я всегда немного нервничаю, когда вижу диспетчер Invoke и блокировку синхронизации потоков одновременно. Синхронный Invoke сам по себе эквивалентен блокировке, так что это возможность тупика. Но до тех пор , как вы осторожны , чтобы только когда - нибудь , что замок внутри диспетчера Invoke вызова (т.е. всегда принимают замки в том же порядке), то это будет хорошо. Просто иногда бывает сложно сделать такую гарантию.
  • 0
    ThreadLock - это пользовательская реализация, которая использует класс Monitor в качестве базы. В ThreadLock просто есть несколько механизмов для отслеживания тупиковых ситуаций и доказано, что это важно для меня. Он генерирует исключение DeadlockException, например, именно в том месте, где я вызываю. В начале у меня был замок вокруг диспетчера, что, конечно, привело к тупику. То есть Invoke автоматически блокируется? Так что я могу смело вызывать эти изменения, не беспокоясь о том, что это все равно может прерваться? Тогда я сохраню оператор блокировки, так как это просто накладные расходы.
Показать ещё 2 комментария
1

Предварительные условия тупика (почему вы еще не видели тупик)

Есть 4 предусловия, которые необходимо выполнить для возникновения тупика. Если один из них отсутствует, тупика не будет. Эти предварительные условия:

  • Взаимное исключение
  • Без предупреждения
  • Держать и ждать
  • Круговое ожидание

Последнюю можно также назвать "Сроки". Поскольку это зависит от того, как Windows назначает процессорное время, вы можете жить без взаимоблокировок в течение многих лет. Скорее всего, это относится к многоядерным процессорам, потому что круговое ожидание легче достичь, если два потока действительно выполняются параллельно.

Ваши символы (почему вы не можете загрузить SOSEX)

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". Я вижу, что все больше разработчиков создают несколько потоков пользовательского интерфейса, то есть потоки, которые имеют собственную очередь сообщений. Надеюсь, у вас есть только один.

  • 0
    Я очень хорошо знаю эти условия, и, конечно же, время будет хорошим объяснением этого события. О символах: Да, я сделал, но только потому, что в нескольких руководствах сказано, что вы должны взять соответствующую DLL-библиотеку .Net, переименовать ее и поместить в каталог WinDbg. Действительно, я отлаживал с 64-битной WinDbg, и мое приложение работает в настоящее время в x86 - я уже скептически относился к этому, но x86 WinDbg не показывал мне потоки (некоторые предупреждения об архитектуре системы или около того), и 64 сделал - я думал, что это может зависит от системы, которую затем использовать. Я посмотрю на эту книгу.
  • 0
    Я бы даже не хотел это менять. Это работает нормально и должно быть асинхронным, потому что я хочу держать пользователя в курсе процесса загрузки. Конечно, только один поток GUI. Даже не знаю, почему я должен использовать другой.
1

Первый шаг - попытаться запустить код синхронно без каких-либо задач.

Второй шаг - проверить, ожидаете ли вы правильно. Например, вам не хватает 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());    
}
  • 0
    Итак, если бы я изменил подпись так, чтобы «MyMethod» возвращал bool, то это, безусловно, не привело бы к тупику (насколько я реализовал блокировку правильно). Верный? Кстати, я пропустил первое ожидание, потому что я пытался запустить его синхронно, и да, это сработало. Я могу знать, что вызывает эти проблемы. Единственное, что вызывается из этих методов, это Splashscreen, и он еще не является поточно-ориентированным. Я изменил его, но так как мне нужно отправить, я получаю там тупики (они на самом деле обнаруживаются). Как только я исправлю это, я могу сказать, что это был Splashscreen. Но эта пустота тоже интересна ...
  • 0
    ОБНОВЛЕНИЕ: Да, это была именно та проблема. Заставка вызвала не найденный тупик. Теперь окно пользовательского интерфейса снова появляется, но там снова блокируется. Я начну основную сессию рефакторинга, чтобы сделать все классы потокобезопасными, что еще не сделано.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню