.NET 11 RC 1 позволяет отправить SIGTERM дочернему процессу без P/Invoke
Signal, WaitForExitStatus и ProcessExitStatus появились прямо в System.Diagnostics.Process в .NET 11 RC 1, поэтому корректное завершение дочернего процесса больше не требует ни P/Invoke к kill, ни обходного пути через SafeProcessHandle.
.NET 11 Release Candidate 1 вышел 2026-09-08 с лицензией go-live. Раздел о библиотеках открывается тем, что раздражало каждого, кто когда-либо управлял дочерним процессом из C#: System.Diagnostics.Process теперь отправляет сигналы POSIX и сообщает, как процесс на самом деле завершился, без спуска на уровень SafeProcessHandle (dotnet/runtime#131165).
Kill() всегда был SIGKILL
Process.Kill() в Unix означает SIGKILL. У дочернего процесса нет ни единого шанса сбросить буфер, закрыть сокет или выполнить обработчик завершения. Для сорвавшегося процесса это нормально, а почти для всего остального неверно: для сервера разработки, для транскодирования ffmpeg, для контейнера Postgres, для тестового стенда, который вы хотите остановить аккуратно.
Обходным путём был P/Invoke:
[LibraryImport("libc", SetLastError = true)]
private static partial int kill(int pid, int sig);
// 15 on Linux and macOS, but you had to know that
kill(process.Id, 15);
Это три строки предположений о платформе, жёстко прописанный номер сигнала и никакой возможности узнать, был ли сигнал действительно доставлен.
Поверхность API в RC 1
RC 1 добавляет в Process четыре члена:
public bool Signal(PosixSignal signal);
public ProcessExitStatus WaitForExitStatus();
public bool TryWaitForExitStatus(TimeSpan timeout, out ProcessExitStatus? exitStatus);
public Task<ProcessExitStatus> WaitForExitStatusAsync(
CancellationToken cancellationToken = default);
Этого достаточно, чтобы корректно написать схему с эскалацией:
using System.Diagnostics;
using System.Runtime.InteropServices;
using Process worker = Process.Start("./worker")!;
if (!worker.Signal(PosixSignal.SIGTERM))
{
// false means the process is already gone, not that the call failed
return;
}
if (!worker.TryWaitForExitStatus(TimeSpan.FromSeconds(10), out ProcessExitStatus? status))
{
worker.Signal(PosixSignal.SIGKILL);
status = worker.WaitForExitStatus();
}
Console.WriteLine($"exit={status.ExitCode} signal={status.Signal}");
ProcessExitStatus прекращает гадание по коду выхода
ProcessExitStatus это небольшой класс, помеченный sealed, с тремя свойствами: ExitCode, Canceled и допускающее null PosixSignal? Signal. Именно допускающий null сигнал здесь и полезен. До сих пор гибель от сигнала приходилось выводить из соглашения 128 + signal number, зашитого в код выхода, а оно молча конфликтует с любым процессом, который штатно завершается с кодом 143. Теперь status.Signal is PosixSignal.SIGTERM отвечает на вопрос напрямую, а Canceled показывает, что ожидание прервалось по вашему тайм-ауту или CancellationToken, а не из-за дочернего процесса.
Это тот же самый ProcessExitStatus, который возвращают Process.RunAndCaptureText и родственные методы и о котором я писал, когда API захвата вывода без взаимоблокировок появились в Preview 4.
Где это не работает
Три ограничения, которые стоит знать до написания кроссплатформенного кода супервизора:
Signalпомечен атрибутами[UnsupportedOSPlatform("ios")]и[UnsupportedOSPlatform("tvos")]. Mac Catalyst поддерживается.- В Windows принимается только
PosixSignal.SIGKILL, и он отображается наTerminateProcess. Любой другой сигнал выбрасываетPlatformNotSupportedException, поэтому путь сначала мягко, затем жёстко требует ветки сOperatingSystem.IsWindows(). - В Unix
WaitForExitStatusработает только для дочернего процесса текущего. Ожидание наProcess, к которому вы подключились по PID, выбрасывает исключение.
Полный список приведён в заметках о выпуске библиотек RC 1. С приложенной лицензией go-live это первый RC, который можно поставить за продакшен-надзор над процессами.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.