Skip to content

Зависание процесса при выходе из приложения на Windows 10 (CPU ~0.1%, музыка продолжает играть) #24

@Scream034

Description

@Scream034

Суть проблемы (в чём баг)

При закрытии плеера на ОС Windows 10 (через крестик главного окна или контекстное меню) приложение визуально исчезает с экрана, иконка в трее пропадает, но процесс LMP.exe не завершается.
В диспетчере задач процесс переходит в состояние «призрака»:

  • Потребление процессора падает практически до нуля (~0.1% CPU).
  • Аномалия: Если в момент закрытия играл трек, музыка может продолжать воспроизводиться ещё некоторое время (или бесконечно), несмотря на то, что графическая оболочка плеера полностью уничтожена.
  • Процесс невозможно завершить стандартными средствами ОС, кроме принудительного снятия через диспетчер задач (Taskkill).

Такое поведение указывает на классический Deadlock (взаимную блокировку) нативного аудио-драйвера Windows (winmm.dll / COM-сервисов) и фонового потока заполнения буфера при некорректной последовательности уничтожения объектов (Shutdown Sequence).

Как воспроизвести (Steps to Reproduce)

  1. Запустить плеер на Windows 10.
  2. Включить любой онлайн-трек (чтобы пошла активная буферизация и работа фонового потока AudioFillBuffer).
  3. Нажать кнопку закрытия окна плеера.
  4. Интерфейс закрывается, иконка из трея пропадает.
  5. Открыть Диспетчер задач Windows -> Найти процесс LMP.exe (он продолжает висеть в фоновых процессах с загрузкой CPU ~0.1%, звук может продолжать играть).

Возможное техническое решение (теория и архитектура)

Проблема вызвана двумя взаимосвязанными архитектурными ошибками:

Ошибка А: async void в обработчике завершения (App.axaml.cs)

desktop.ShutdownRequested += async (_, _) => { ... };

Событие ShutdownRequested в Avalonia возвращает void. Использование async void лямбды приводит к тому, что Avalonia уничтожает графическое окно, закрывает цикл сообщений (Message Loop) и даёт ОС сигнал на закрытие процесса до того, как завершатся асинхронные вызовы DisposeAsync внутри лямбды. В итоге AudioEngine и AudioPlayer вообще не успевают финализироваться.

Ошибка Б: Дедлок при финализации WaveOutEvent в NAudio

Если процесс начинает аварийно завершаться на уровне CLR, срабатывают деструкторы (Finalizers). Нативный драйвер winmm.dll пытается освободить буферы WaveOutEvent.
В этот момент наш фоновый поток AudioFillBuffer в NAudioBackend.cs может стоять на блокировке lock-объекта _stateLock или быть заблокированным внутри _provider.AddSamples() (который ждёт освобождения ресурсов драйвером). Возникает перекрёстный дедлок:

  • Поток UI/Finalizer ждёт завершения _fillThread.Join().
  • Поток _fillThread заблокирован в нативном вызове, ожидающем ответа от аудио-драйвера, который заблокирован потоком UI.

Шаги к решению:

  1. Сделать Shutdown синхронным и гарантированным:
    В App.axaml.cs в обработчике ShutdownRequested отказаться от async void. Нам нужно принудительно и строго синхронно (блокируя поток закрытия на долю секунды) вызвать Dispose на всей цепочке:
    AudioEngine.Dispose() -> AudioPlayer.Dispose() -> NAudioBackend.Dispose().
  2. Безопасный выход из цикла заполнения (NAudioBackend.cs):
    В методе Dispose() бэкенда сначала вызывать деактивацию и отмену токена _cts.Cancel(), будить поток через _fillWakeup.Set(), дожидаться гарантированного завершения _fillThread.Join() и только после этого вызывать _waveOut.Stop() и _waveOut.Dispose().
    Это разорвёт петлю дедлока: на момент уничтожения аудио-устройства наш фоновый поток гарантированно прекратит писать данные в буферы.

Окружение (Environment)

  • Версия LMP: 220+
  • Версия ОС: Windows 10 (Build 19045+)
  • Остальные настройки вроде не влияют.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions