بهبودهای API فرآیند در NET 11.
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۳/۱۰ ۱۲:۱۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
System.Diagnostics.Process اصلیترین راهکار .NET برای ایجاد و تعامل با فرآیندهای سیستمعامل است. در نسخه .NET 11، این کلاس یکی از بزرگترین بهروزرسانیهای خود را در سالهای اخیر، دریافت کردهاست. این تغییرات شامل APIهای سطح بالا برای شروع فرآیند و خواندن خروجی بدون ایجاد بنبست، کنترل کامل بر ارثبری هندلها، ویژگیهای مدیریت طول عمر فرآیند (مانند KillOnParentExit) و APIهای سبک مبتنی بر SafeProcessHandle است که برای سناریوهای NativeAOT بسیار مناسبتر هستند. هدف اصلی این بهروزرسانیها، سادهتر کردن کدهای رایج، افزایش قابلیت اطمینان و بهبود عملکرد قابل توجهاست. در ادامه، به مهمترین ویژگیهای جدید آن میپردازیم.RedirectStandardOutput و RedirectStandardError را فعال میکنید، .NET دو پایپ (pipe) ایجاد میکند. پایپها بافر محدودی دارند (معمولاً ۴ کیلوبایت در ویندوز و ۶۴ کیلوبایت در یونیکس). اگر فرآیند فرزند، دادهی بیشتری از اندازه بافر بنویسد و فرآیند والد، همزمان در حال خواندن نباشد، فرآیند فرزند، روی عملیات نوشتن بلاک میشود.process.Start(); process.WaitForExit(); string output = process.StandardOutput.ReadToEnd(); // ممکن است بلاک شود string error = process.StandardError.ReadToEnd();
ReadToEnd تا زمان بسته شدن پایپ (خروج فرآیند) بلاک میماند. راهحل، خواندن همزمان هر دو جریان است.public (string StandardOutput, string StandardError) ReadAllText(TimeSpan? timeout = default); public Task<(string StandardOutput, string StandardError)> ReadAllTextAsync(CancellationToken ct = default); public IEnumerable<ProcessOutputLine> ReadAllLines(TimeSpan? timeout = default); public IAsyncEnumerable<ProcessOutputLine> ReadAllLinesAsync(CancellationToken ct = default); public (byte[] StandardOutput, byte[] StandardError) ReadAllBytes(TimeSpan? timeout = default);
using Process process = Process.Start(new ProcessStartInfo("dotnet", "--help")
{
RedirectStandardOutput = true,
RedirectStandardError = true
})!;
(string output, string error) = process.ReadAllText();RunAndCaptureText همهی مراحل را در یک فراخوانی انجام میدهد:ProcessTextOutput result = Process.RunAndCaptureText("dotnet", ["--help"]);
Console.WriteLine(result.StandardOutput);
Console.WriteLine($"Exit Code: {result.ExitStatus.ExitCode}");ProcessExitStatus) را برمیگرداند.ProcessStartInfo.InheritedHandles: مشخص کردن دقیق هندلهایی که باید ارثبری شوند.ProcessStartInfo.StandardInputHandle / StandardOutputHandle / StandardErrorHandle: تغییر مسیر مستقیم به هر SafeFileHandle دلخواه (فایل، پایپ، null و غیره).SafeFileHandle.CreateAnonymousPipe(out var readPipe, out var writePipe);
using (readPipe)
using (writePipe)
{
var producer = new ProcessStartInfo("ls", ["/usr/bin"]) { StandardOutputHandle = writePipe };
var consumer = new ProcessStartInfo("grep", ["zip"])
{
StandardInputHandle = readPipe,
StandardOutputHandle = File.OpenHandle("output.txt", FileMode.Create, FileAccess.Write)
};
using var p1 = Process.Start(producer)!;
writePipe.Dispose(); // والد دیگر نیازی به آن ندارد
using var p2 = Process.Start(consumer)!;
readPipe.Dispose();
await Task.WhenAll(p1.WaitForExitAsync(), p2.WaitForExitAsync());
}Process.StartAndForget: فرآیند را شروع میکند، PID را برمیگرداند و بلافاصله همه منابع را آزاد میکند.ProcessStartInfo.KillOnParentExit: وقتی فرآیند والد خارج شود، فرآیندهای فرزند بهطور خودکار متوقف میشوند (ویندوز و لینوکس).ProcessStartInfo.StartDetached: فرآیند کاملاً مستقل از والد اجرا میشود و حتی با خروج والد یا بستن ترمینال زنده میماند.SafeProcessHandle حالا APIهای عمومی غنیتری دارد:using SafeProcessHandle handle = SafeProcessHandle.Start(startInfo); ProcessExitStatus status = handle.WaitForExitOrKillOnTimeout(TimeSpan.FromMinutes(2));
BeginOutputReadLine و BeginErrorReadLine نخهای Thread Pool را بلاک نمیکنند؛ نتیجه: مقیاسپذیری بسیار بهتر، هنگام اجرای موازی فرآیندها.posix_spawn به جای fork+exec؛ نتیجه: تا ۱۰۰ برابر سریعتر شدن ایجاد فرآیند.RunAndCaptureText، کنترل دقیق ارثبری هندلها و KillOnParentExit دقیقاً همان چیزهایی هستند که توسعهدهندگان سالها منتظر آن بودند.System.Diagnostics.Process در .NET 11 پرداختیم؛ از جمله روشهای جدید برای خواندن خروجی بدون بنبست (ReadAllText، RunAndCaptureText)، مدیریت ارثبری هندلها، ویژگیهای طول عمر فرآیند و بهبودهای عملکردی.Run و RunAsync و شروع فرآیند بدون انتظار با StartAndForget.RunوRunAsyncvar startInfo = new ProcessStartInfo
{
FileName = "pwd",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(startInfo);
await process.WaitForExitAsync();
if (process.ExitCode == 0)
Log.Information("Success!");
else
Log.Error("Failed!");var result = await Process.RunAsync("pwd");
if (result.ExitCode == 0)
Log.Information("Success");
else
Log.Error("Failure");var result = await Process.RunAsync("pwd", ["-L"]);Run (نسخه synchronous) و RunAsync مستقیماً یک شیء ProcessExitStatus برمیگردانند که شامل ExitCode، Signal (در سیستمهای یونیکس) و پرچم Canceled است. این متدها مناسب مواردی هستند که فقط به نتیجه اجرای فرآیند نیاز دارید و نیازی به خواندن خروجی ندارید.StartAndForget)Process.Start(new ProcessStartInfo
{
FileName = "ping",
Arguments = "-v google.com",
CreateNoWindow = true
});StartAndForget اضافه شده است:Process.StartAndForget("ping", ["-v", "example.com"]);Process را آزاد میکند و کد آن کاملاً شفاف است: «شروع کن و فراموشش کن»Run/RunAsync و StartAndForget، .NET 11 تقریباً تمام سناریوهای رایج کار با فرآیندهای خارجی را پوشش داده است:RunAsyncRunAndCaptureTextAsyncStartAndForgetSafeProcessHandle و APIهای هندلProcess، خطر بنبست (Deadlock) بود. کد زیر نمونهای از پیادهسازی ناامن در .NET 10 است:using Process process = Process.Start(new ProcessStartInfo("git", ["status"])
{
RedirectStandardOutput = true,
RedirectStandardError = true
})!;
// ⚠️ خطر بنبست!
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();ReadToEnd() روی یک جریان بلاک میشود تا فرآیند خارج شود.stderr بنویسد، منتظر خوانده شدن آن میماند.stdout منتظر میماند: بنبست متقابل.ReadToEndAsync() + Task.WhenAllOutputDataReceived و ErrorDataReceived همراه با BeginOutputReadLine()ReadAllText / ReadAllTextAsyncReadToEnd:using Process process = Process.Start(startInfo)!; (string output, string error) = await process.ReadAllTextAsync(); // یا نسخه synchronous: process.ReadAllText(timeout) process.WaitForExit();
ReadAllLines / ReadAllLinesAsync — خواندن خط به خط (ایدهآل برای پردازش realtime)using Process process = Process.Start(startInfo)!;
await foreach (ProcessOutputLine line in process.ReadAllLinesAsync())
{
if (line.StandardError)
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(line.Content);
Console.ResetColor();
}ProcessOutputLine یک readonly struct ساده شامل Content و StandardError است.ReadAllBytes / ReadAllBytesAsync(byte[] output, byte[] error) = await process.ReadAllBytesAsync();
RunAndCaptureText / RunAndCaptureTextAsync — همه چیز در یک فراخوانیProcessTextOutput result = await Process.RunAndCaptureTextAsync("git", ["status"]);
if (result.ExitStatus.ExitCode == 0)
Log.Information(result.StandardOutput);
else
Log.Error(result.StandardError);ArrayPool برای کاهش تخصیص حافظه بهره میبرد.RunAndCaptureTextAsync شروع کنید.ReadAllLinesAsync بهترین انتخاب است.timeout یا cancellationToken استفاده کنید تا برنامهتان در برابر فرآیندهای هنگکننده مقاوم باشد.SafeProcessHandle یا هندلهای مستقیم استفاده کنید.StartAndForget برای شروع فرآیند و فراموش کردن آن پرداختیم. با این حال، یک محدودیت مهم وجود داشت: بسیاری از متدهای Run، RunAndCaptureText و مشابه آنها، هنگام خاتمه یافتن برنامه اصلی (فرآیند والد)، فرآیندهای فرزند را نیز بهطور خودکار متوقف میکنند. این رفتار معمولاً مطلوب است، اما در برخی سناریوها نیاز داریم فرآیند فرزند مستقل بماند و حتی پس از خروج برنامه اصلی به کار خود ادامه دهد. .NET 11 با افزودن ویژگی StartDetached این نیاز را بهسادگی برآورده کرده است.ProcessStartInfo تنظیم میکنید و سپس با StartAndForget (یا متدهای دیگر) استفاده مینمایید:using System.Diagnostics;
var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "google.com",
StartDetached = true, // تنظیم کلیدی
UseShellExecute = false,
CreateNoWindow = true
};
Process.StartAndForget(startInfo);StartDetached را true قرار میدهید، فرآیند فرزند از والد جدا (detached) میشود و با خروج والد، دریافت سیگنال یا بسته شدن ترمینال، به کار خود ادامه میدهد.Process.StartAndForget و هم در Process.Start قابل استفاده است.StartDetached فعال باشد و تغییر مسیری برای استاندارد هندلها مشخص نشده باشد، بهطور خودکار به null handle تغییر مسیر داده میشوند تا وابستگی غیرضروری ایجاد نشود.DETACHED_PROCESS و در یونیکس با setsid).StartDetached همراه با KillOnParentExit = false (مقدار پیشفرض) تضمین میکند فرآیند فرزند واقعاً مستقل بماند.StartAndForget و StartDetached در .NET 11 کنترل دقیق و آسانی روی طول عمر و استقلال فرآیندهای فرزند فراهم میکند. این ویژگیها کد را تمیز و خوانا نگه میدارند و انعطافپذیری بالایی به توسعهدهندگان میدهند. اگر قبلاً برای ایجاد فرآیندهای مستقل از فلگهای دستی و پلتفرممحور استفاده میکردید، حالا میتوانید با کد portable و سادهتری این کار را انجام دهید. این قابلیت از نسخههای Preview .NET 11 در دسترس است و مکمل عالی ویژگیهای مدیریت طول عمر فرآیند به شمار میرود.