Особенности Microsoft.Win32.SystemEvents в приложении с несколькими AppDomain

    Жило-было консольное приложение, которое создавало appdomain и запускало в нём сервис на основе Topshelf. Но однажды в нём появился баг – после нажатия Ctrl-C оно не завершало свою работу, а сообщало о получении команды на выключение и зависало в таком состоянии. Краткий анализ показал, что зависало оно на выгрузке домена, но, что странно, не выкидывало исключение CannotUnloadAppDomainException.

    Далее было замечено, что приложение зависает при закрытии только после выполнении определенных задач, и при этом есть несколько «лишних» потоков перед выгрузкой домена, которых не наблюдается в случае нормального завершения приложения. Как можно догадаться из названия, дело оказалось в SystemEvents. При использовании System.Drawing.SolidBrush происходит добавление обработчика в SystemEvents.UserPreferenceChanging, но так как это отдельный appdomain, инициализация типа происходит еще раз, создается еще один поток с именем ".NET SystemEvents", который при старте добавляет в обработчики консоли свой.
    При закрытии приложения вызывается SystemEvents.Dispose(), и в нём при удалении обработчика консоли UnsafeNativeMethods.SetConsoleCtrlHandler(consoleHandler, 0) приложение зависает. Я нагуглил пару упоминаний о проблеме с удалением обработчиков консоли, но все решения сводились к вызову SystemEvents.Shutdown() через Reflection, но мне это не помогло, да и не должно вроде помогать, оно ж потом вызывает Dispose().
    Тогда я решил, что нужно каким то образом избежать создания второго потока SystemEvents, и лазейка нашлась в методе SystemEvents.EnsureSystemEvents(bool requireHandle, bool throwOnRefusal):

    if (Thread.GetDomain().GetData(".appDomain") != null) {
      if (throwOnRefusal) {
        throw new InvalidOperationException(SR.GetString(SR.ErrorSystemEventsNotSupported));
      }
      return;
    }
    //тут идёт инициализация SystemEvents
    

    В моём случае этот метод вызывался с throwOnRefusal = false, поэтому добавление domain.SetData(".appDomain", new object()) позволяет избежать создания еще одного потока SystemEvents и зависания при удалении его обработчика консоли, приложение корректно завершает свою работу.
    Возможен побочный эффект в случае кода, который опирается на проверку свойства ".appDomain", он может посчитать наше приложение за asp.net )) Если кто то знает, какие проблемы могут быть вызваны этой настройкой, или знает более безопасное и подходящее решение проблемы, прошу поделиться.
    Share post

    Similar posts

    Comments 0

    Only users with full accounts can post comments. Log in, please.