Дочерние процессы, созданные в ASP.NET Core Process, уничтожаются при выходе

Я порождаю дочерний процесс в ASP.NET Core (.NET Framework) с классом Process:

var process = new Process
            {
                StartInfo = new ProcessStartInfo(executableDir)
                {
                    Arguments = commandDefinition.CommandDef.ArgumentsAsString,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                    WorkingDirectory = _contentPath,
                },
            };

process.Start()

Насколько я понимаю, когда родительский (ASP.Net Core) процесс будет убит, дочерний процесс должен остаться в живых. Я проверил это поведение с помощью двух консольных приложений, и дочерний процесс никогда не уничтожается после уничтожения родительского процесса. Однако, когда я запускаю новый процесс в ASP.NET Core, дочерний процесс убивается, когда:

  • IIS перерабатывает приложение.
  • MSDeploy публикует новую версию приложения ASP.NET Core.
  • При использовании dotnet watch и приложение перезапускается во время изменения кода.

Он не убивается ТОЛЬКО, если родитель убит через диспетчер задач. (После некоторых тестов это не всегда так)

Из вышесказанного я подозреваю, что в ASP.NET Core есть механизм, который убивает все дочерние процессы при успешном завершении. Это где-то задокументировано? Есть ли способ избежать этого? Я не мог найти никакой информации о таком поведении.

Редактировать: на самом деле довольно легко.

  1. Создать проект ASP.NET Core (.NET Framework или .NET Core, не имеет значения)
  2. Добавьте код ниже в свой класс Startup
  3. Запустите веб-приложение. Он будет размещен в IIS Express. Процесс вычисления начнется. Теперь либо убейте свое приложение через диспетчер задач, либо закройте его с помощью значка IIS Express на панели задач.
  4. Процесс Calc будет убит. (Иногда вам нужно обновить свою офлайн-страницу)
 var process = new Process
 {
      StartInfo = new ProcessStartInfo("calc.exe")
      {
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
      },
 };
 process.Start();

Edit2: проблема, кажется, в IIS. У меня есть два профиля в файле launchSettings.json. Если я запускаю его с IISExpress, то он закрывается, однако при использовании второго он живет.

"IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "WebApplication3Core": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:52135/"
    }

Edit4:

Я провел некоторые исследования с монитором процесса, и вот результат:

ss1

ss2

ss3

Как вы можете видеть в ss1, что есть операция "Выход из процесса" с iisexpress, тогда есть много ненужных журналов и через некоторое время есть выход из процесса для calc.exe. Это ничем не отличается от нормального выхода. Разница лишь в последнем журнале, который говорит "CloseFile" и путь к моему веб-приложению, я не знаю, что это на самом деле означает. Это определенно тот, кто убивает calc.exe. У меня есть версия IIS Express 10.0.14358 (версия сервера, где я нашел это также 10)

Ответы

Ответ 1

Как я уже сказал в своем комментарии, существует объект Job, который создается ядром ASP.NET в сценарии с отсутствием процесса. Соответствующая часть исходного кода находится здесь:

https://github.com/aspnet/AspNetCore/blob/master/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp#L89

HRESULT
SERVER_PROCESS::SetupJobObject(VOID)
{
    HRESULT                                 hr = S_OK;
    JOBOBJECT_EXTENDED_LIMIT_INFORMATION    jobInfo = { 0 };

    if (m_hJobObject == NULL)
    {
      ....
            jobInfo.BasicLimitInformation.LimitFlags =
                JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

            if (!SetInformationJobObject(m_hJobObject,
                JobObjectExtendedLimitInformation,
                &jobInfo,
                sizeof jobInfo))
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
        }
    }

    return hr;
}

Согласно документации, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE:

Вызывает завершение всех процессов, связанных с заданием, когда закрывается последний дескриптор задания.

Если вы копаете источник дальше, он не является необязательным.

Теперь вы можете увидеть это сами, если воспроизведете свои шаги. Используйте Process Explorer и перейдите к процессу dotnet.exe, вот что он отобразит:

enter image description here

  • примечание 1: на самом деле calc.exe остается живым (по крайней мере, в моей установке Windows 10, потому что теперь это приложение WinRT, поэтому оно не станет дочерним по отношению к dotnet.exe), поэтому я использовал notepad.exe
  • примечание 2: iisexpress.exe также создает объект задания, но он настроен как отколовшийся, что означает, что он не будет убивать дочерние процессы.
  • Примечание 3: если вы работаете с Visual Studio (не мой скриншот), вы можете увидеть промежуточный VSIISExeLauncher.exe процесс между iisexpress.exe и dotnet.exe. Этот также создает объект Job с "kill on close", чтобы добавить к путанице...

Ответ 2

редактировать

Решение не работает для notepad.exe - я постараюсь выяснить, есть ли другой вариант, чем решение Саймона, но сейчас его ответ, кажется, будет верным.

Старый ответ (будет удален)

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

В любом случае это может сработать для вас (вставив cmd.exe между ними, чтобы попытаться разорвать отношения между детьми и родителями):

var process = new Process
{
  StartInfo = new ProcessStartInfo 
      { 
         FileName ="cmd.exe",  
         Arguments = "/c calc.exe", 
         WindowStyle =  ProcessWindowStyle.Hidden
      }
};

process.Start();

Ответ 3

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