क्या प्रोसेस.स्टार्ट के समरूप कोई एसिंक्स है?


141

जैसे शीर्षक बताता है, क्या इसके बराबर है Process.Start(आप किसी अन्य एप्लिकेशन या बैच फ़ाइल को चलाने की अनुमति देता है) जिसका मैं इंतजार कर सकता हूं?

मैं एक छोटे कंसोल ऐप के साथ खेल रहा हूं और यह ऐसा प्रतीत होता है कि यह सही जगह है जो एसिंक्स और वेट का उपयोग करता है लेकिन मुझे इस परिदृश्य के लिए कोई दस्तावेज नहीं मिल रहा है।

मैं क्या सोच रहा हूँ इन पंक्तियों के साथ कुछ:

void async RunCommand()
{
    var result = await Process.RunAsync("command to run");
}

2
आप सिर्फ लौटे प्रक्रिया ऑब्जेक्ट पर WaitForExit का उपयोग क्यों नहीं करेंगे?
सिंपलवर

2
और वैसे भी, लगता है कि आप "सिंक" समाधान के बजाय "सिंक" समाधान की तलाश कर रहे हैं, इसलिए शीर्षक भ्रामक है।
सिंपलवर

2
@YoryeNathan - योग्य। दरअसल, Process.Start है async और ओपी एक तुल्यकालिक संस्करण चाहते प्रतीत होता है।
Oded

10
ओपी C # 5
एक्विनास

4
ठीक है, मैंने अपनी पोस्ट को थोड़ा और स्पष्ट किया है। मैं यह क्यों चाहता हूं, इसका स्पष्टीकरण सरल है। ऐसा परिदृश्य देखें जिसमें आपको एक बाहरी कमांड (7zip जैसी कोई चीज़) चलाना हो और फिर एप्लिकेशन का प्रवाह जारी रखें। यह ठीक वैसा ही है जैसा कि async / wait को सुविधाजनक बनाने के लिए किया गया था और अभी तक यह प्रक्रिया को चलाने के लिए कोई रास्ता नहीं है और इसके बाहर निकलने का इंतजार कर रहा है।
लिंकरो

जवाबों:


196

Process.Start()केवल प्रक्रिया शुरू होती है, यह खत्म होने तक इंतजार नहीं करता है, इसलिए इसे बनाने के लिए बहुत मायने नहीं रखता है async। यदि आप अभी भी इसे करना चाहते हैं, तो आप कुछ ऐसा कर सकते हैं await Task.Run(() => Process.Start(fileName))

लेकिन, यदि आप एसिंक्रोनस रूप से समाप्त करने के लिए प्रक्रिया के लिए इंतजार करना चाहते हैं, तो आप उपयोग कर सकते हैं घटना के साथ मिलकर :ExitedTaskCompletionSource

static Task<int> RunProcessAsync(string fileName)
{
    var tcs = new TaskCompletionSource<int>();

    var process = new Process
    {
        StartInfo = { FileName = fileName },
        EnableRaisingEvents = true
    };

    process.Exited += (sender, args) =>
    {
        tcs.SetResult(process.ExitCode);
        process.Dispose();
    };

    process.Start();

    return tcs.Task;
}

36
मैं आखिरकार इसके लिए गितुब पर कुछ चिपकाने के लिए मिला - इसका कोई रद्दीकरण / टाइमआउट समर्थन नहीं है, लेकिन यह कम से कम आपके लिए मानक आउटपुट और मानक त्रुटि को इकट्ठा करेगा। github.com/jamesmanning/RunProcessAsTask
जेम्स मैनिंग

3
यह कार्यक्षमता में भी उपलब्ध है MedallionShell NuGet पैकेज
ChaseMedallion

8
वास्तव में महत्वपूर्ण: वह क्रम जिसमें आप विभिन्न गुणों को सेट करते हैं processऔर process.StartInfoजब आप इसे चलाते हैं तो क्या होता है .Start()। यदि आप उदाहरण के लिए गुणों को .EnableRaisingEvents = trueसेट करने से पहले कहते StartInfoहैं जैसा कि यहां देखा गया है, तो चीजें अपेक्षित रूप से काम करती हैं। यदि आप इसे बाद में सेट करते हैं, उदाहरण के लिए इसे साथ रखने के लिए .Exited, भले ही आप इसे पहले कहते हैं .Start(), यह ठीक से काम करने में विफल रहता है - .Exitedप्रक्रिया के वास्तव में बाहर निकलने के लिए इंतजार करने के बजाय तुरंत आग। पता नहीं क्यों, सिर्फ सावधानी का एक शब्द।
बजे क्रिस मोसचिनी

2
@svick विंडो फॉर्म में, process.SynchronizingObjectउन तरीकों से बचने के लिए घटक बनाने के लिए सेट किया जाना चाहिए जो ईवेंट्स को हैंडल करने के लिए (जैसे कि एक्ज़िटेड, आउटपुटडायट्राइसेड, एररडॉट्रेडाइसेव्ड) को अलग थ्रेड पर कहा जाता है।
केविनबाई

4
यह है वास्तव में समझ में रैप करने के लिए बनाने के Process.Startमें Task.Run। एक UNC पथ, उदाहरण के लिए, समकालिक रूप से हल किया जाएगा। इस स्निपेट को पूरा करने में 30 सेकंड तक लग सकते हैं:Process.Start(@"\\live.sysinternals.com\whatever")
जबे

55

यहाँ मेरा ले रहा है, svick के जवाब पर आधारित है । यह आउटपुट रिडायरेक्शन, एक्ज़िट कोड रिटेंशन और थोड़ी बेहतर एरर हैंडलिंग को जोड़ता है ( Processऑब्जेक्ट को डिस्पोज़ करना भले ही शुरू नहीं किया जा सकता):

public static async Task<int> RunProcessAsync(string fileName, string args)
{
    using (var process = new Process
    {
        StartInfo =
        {
            FileName = fileName, Arguments = args,
            UseShellExecute = false, CreateNoWindow = true,
            RedirectStandardOutput = true, RedirectStandardError = true
        },
        EnableRaisingEvents = true
    })
    {
        return await RunProcessAsync(process).ConfigureAwait(false);
    }
}    
private static Task<int> RunProcessAsync(Process process)
{
    var tcs = new TaskCompletionSource<int>();

    process.Exited += (s, ea) => tcs.SetResult(process.ExitCode);
    process.OutputDataReceived += (s, ea) => Console.WriteLine(ea.Data);
    process.ErrorDataReceived += (s, ea) => Console.WriteLine("ERR: " + ea.Data);

    bool started = process.Start();
    if (!started)
    {
        //you may allow for the process to be re-used (started = false) 
        //but I'm not sure about the guarantees of the Exited event in such a case
        throw new InvalidOperationException("Could not start process: " + process);
    }

    process.BeginOutputReadLine();
    process.BeginErrorReadLine();

    return tcs.Task;
}

1
बस यह दिलचस्प समाधान मिला। जैसा कि मैं c # नया हूं, मुझे यकीन नहीं है कि कैसे उपयोग करें async Task<int> RunProcessAsync(string fileName, string args)। मैंने इस उदाहरण को अनुकूलित किया और एक-एक करके तीन वस्तुओं को पास किया। मैं घटनाओं को बढ़ाने का इंतजार कैसे कर सकता हूं? जैसे। मेरे आवेदन को रोकने से पहले .. बहुत बहुत धन्यवाद
marrrschine

3
@marrrschine मुझे ठीक से समझ में नहीं आ रहा है कि आपका क्या मतलब है, शायद आपको कुछ कोड के साथ एक नया सवाल शुरू करना चाहिए ताकि हम देख सकें कि आपने क्या कोशिश की और वहाँ से जारी रहे।
ओहद श्नाइडर

4
शानदार जवाब। ग्राउंडवर्क बिछाने के लिए धन्यवाद svick और इस बहुत उपयोगी विस्तार के लिए धन्यवाद Ohad।
गॉर्डन बीन

1
कोड (पढ़ने @SuperJMN referencesource.microsoft.com/#System/services/monitoring/... ) मैं नहीं मानता कि Disposenulls ईवेंट हैंडलर, तो सैद्धांतिक रूप से यदि आप कहा जाता है Dispose, लेकिन चारों ओर संदर्भ रखा, मुझे विश्वास है कि एक रिसाव हो सकता है। हालाँकि, जब Processऑब्जेक्ट के लिए अधिक संदर्भ नहीं होते हैं और इसे एकत्र (कचरा) हो जाता है, तो कोई भी ऐसा नहीं है जो ईवेंट हैंडलर सूची को इंगित करता है। इसलिए यह एकत्र हो जाता है, और अब प्रतिनिधियों के लिए कोई संदर्भ नहीं है जो सूची में हुआ करते थे, इसलिए अंत में वे कचरा एकत्र करते हैं।
ओहद श्नाइडर

1
@SuperJMN: दिलचस्प बात यह है कि यह उससे कहीं अधिक जटिल / शक्तिशाली है। एक के लिए, Disposeकुछ संसाधनों को साफ करता है, लेकिन एक लीक हुए संदर्भ को processआसपास रखने से नहीं रोकता है । वास्तव में, आप देखेंगे कि processहैंडलर को संदर्भित करता है, लेकिन Exitedहैंडलर का संदर्भ भी है process। कुछ प्रणालियों में, यह परिपत्र संदर्भ कचरा संग्रहण को रोक देगा, लेकिन .NET में उपयोग किए गए एल्गोरिथ्म अभी भी इसे सभी को तब तक साफ करने की अनुमति देगा जब तक कि सब कुछ "द्वीप" पर रहता है, जिसमें कोई बाहरी संदर्भ नहीं है।
रुबेरडक

4

यहाँ एक और दृष्टिकोण है। Svick और Ohad के उत्तरों के लिए समान अवधारणा लेकिन Processप्रकार पर एक एक्सटेंशन विधि का उपयोग करना ।

विस्तार विधि:

public static Task RunAsync(this Process process)
{
    var tcs = new TaskCompletionSource<object>();
    process.EnableRaisingEvents = true;
    process.Exited += (s, e) => tcs.TrySetResult(null);
    // not sure on best way to handle false being returned
    if (!process.Start()) tcs.SetException(new Exception("Failed to start process."));
    return tcs.Task;
}

एक उदाहरण विधि में उदाहरण उपयोग मामला:

public async Task ExecuteAsync(string executablePath)
{
    using (var process = new Process())
    {
        // configure process
        process.StartInfo.FileName = executablePath;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.CreateNoWindow = true;
        // run process asynchronously
        await process.RunAsync();
        // do stuff with results
        Console.WriteLine($"Process finished running at {process.ExitTime} with exit code {process.ExitCode}");
    };// dispose process
}

4

मैंने एक प्रक्रिया शुरू करने के लिए एक वर्ग बनाया है और यह विभिन्न आवश्यकताओं के कारण पिछले वर्षों में बढ़ रहा था। उपयोग के दौरान मुझे प्रोसेस क्लास के साथ कई मुद्दों का पता चला और साथ ही एक्सिटकोड को पढ़ना भी पड़ा। तो यह सब मेरी कक्षा द्वारा तय किया गया है।

कक्षा में कई संभावनाएं हैं, उदाहरण के लिए आउटपुट पढ़ना, व्यवस्थापक या अलग-अलग उपयोगकर्ता के रूप में शुरू करना, अपवादों को पकड़ना और यह सभी अतुल्यकालिक झुकाव भी शुरू करना। रद्द करना। अच्छा यह है कि निष्पादन के दौरान रीडिंग आउटपुट भी संभव है।

public class ProcessSettings
{
    public string FileName { get; set; }
    public string Arguments { get; set; } = "";
    public string WorkingDirectory { get; set; } = "";
    public string InputText { get; set; } = null;
    public int Timeout_milliseconds { get; set; } = -1;
    public bool ReadOutput { get; set; }
    public bool ShowWindow { get; set; }
    public bool KeepWindowOpen { get; set; }
    public bool StartAsAdministrator { get; set; }
    public string StartAsUsername { get; set; }
    public string StartAsUsername_Password { get; set; }
    public string StartAsUsername_Domain { get; set; }
    public bool DontReadExitCode { get; set; }
    public bool ThrowExceptions { get; set; }
    public CancellationToken CancellationToken { get; set; }
}

public class ProcessOutputReader   // Optional, to get the output while executing instead only as result at the end
{
    public event TextEventHandler OutputChanged;
    public event TextEventHandler OutputErrorChanged;
    public void UpdateOutput(string text)
    {
        OutputChanged?.Invoke(this, new TextEventArgs(text));
    }
    public void UpdateOutputError(string text)
    {
        OutputErrorChanged?.Invoke(this, new TextEventArgs(text));
    }
    public delegate void TextEventHandler(object sender, TextEventArgs e);
    public class TextEventArgs : EventArgs
    {
        public string Text { get; }
        public TextEventArgs(string text) { Text = text; }
    }
}

public class ProcessResult
{
    public string Output { get; set; }
    public string OutputError { get; set; }
    public int ExitCode { get; set; }
    public bool WasCancelled { get; set; }
    public bool WasSuccessful { get; set; }
}

public class ProcessStarter
{
    public ProcessResult Execute(ProcessSettings settings, ProcessOutputReader outputReader = null)
    {
        return Task.Run(() => ExecuteAsync(settings, outputReader)).GetAwaiter().GetResult();
    }

    public async Task<ProcessResult> ExecuteAsync(ProcessSettings settings, ProcessOutputReader outputReader = null)
    {
        if (settings.FileName == null) throw new ArgumentNullException(nameof(ProcessSettings.FileName));
        if (settings.Arguments == null) throw new ArgumentNullException(nameof(ProcessSettings.Arguments));

        var cmdSwitches = "/Q " + (settings.KeepWindowOpen ? "/K" : "/C");

        var arguments = $"{cmdSwitches} {settings.FileName} {settings.Arguments}";
        var startInfo = new ProcessStartInfo("cmd", arguments)
        {
            UseShellExecute = false,
            RedirectStandardOutput = settings.ReadOutput,
            RedirectStandardError = settings.ReadOutput,
            RedirectStandardInput = settings.InputText != null,
            CreateNoWindow = !(settings.ShowWindow || settings.KeepWindowOpen),
        };
        if (!string.IsNullOrWhiteSpace(settings.StartAsUsername))
        {
            if (string.IsNullOrWhiteSpace(settings.StartAsUsername_Password))
                throw new ArgumentNullException(nameof(ProcessSettings.StartAsUsername_Password));
            if (string.IsNullOrWhiteSpace(settings.StartAsUsername_Domain))
                throw new ArgumentNullException(nameof(ProcessSettings.StartAsUsername_Domain));
            if (string.IsNullOrWhiteSpace(settings.WorkingDirectory))
                settings.WorkingDirectory = Path.GetPathRoot(Path.GetTempPath());

            startInfo.UserName = settings.StartAsUsername;
            startInfo.PasswordInClearText = settings.StartAsUsername_Password;
            startInfo.Domain = settings.StartAsUsername_Domain;
        }
        var output = new StringBuilder();
        var error = new StringBuilder();
        if (!settings.ReadOutput)
        {
            output.AppendLine($"Enable {nameof(ProcessSettings.ReadOutput)} to get Output");
        }
        if (settings.StartAsAdministrator)
        {
            startInfo.Verb = "runas";
            startInfo.UseShellExecute = true;  // Verb="runas" only possible with ShellExecute=true.
            startInfo.RedirectStandardOutput = startInfo.RedirectStandardError = startInfo.RedirectStandardInput = false;
            output.AppendLine("Output couldn't be read when started as Administrator");
        }
        if (!string.IsNullOrWhiteSpace(settings.WorkingDirectory))
        {
            startInfo.WorkingDirectory = settings.WorkingDirectory;
        }
        var result = new ProcessResult();
        var taskCompletionSourceProcess = new TaskCompletionSource<bool>();

        var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true };
        try
        {
            process.OutputDataReceived += (sender, e) =>
            {
                if (e?.Data != null)
                {
                    output.AppendLine(e.Data);
                    outputReader?.UpdateOutput(e.Data);
                }
            };
            process.ErrorDataReceived += (sender, e) =>
            {
                if (e?.Data != null)
                {
                    error.AppendLine(e.Data);
                    outputReader?.UpdateOutputError(e.Data);
                }
            };
            process.Exited += (sender, e) =>
            {
                try { (sender as Process)?.WaitForExit(); } catch (InvalidOperationException) { }
                taskCompletionSourceProcess.TrySetResult(false);
            };

            var success = false;
            try
            {
                process.Start();
                success = true;
            }
            catch (System.ComponentModel.Win32Exception ex)
            {
                if (ex.NativeErrorCode == 1223)
                {
                    error.AppendLine("AdminRights request Cancelled by User!! " + ex);
                    if (settings.ThrowExceptions) taskCompletionSourceProcess.SetException(ex); else taskCompletionSourceProcess.TrySetResult(false);
                }
                else
                {
                    error.AppendLine("Win32Exception thrown: " + ex);
                    if (settings.ThrowExceptions) taskCompletionSourceProcess.SetException(ex); else taskCompletionSourceProcess.TrySetResult(false);
                }
            }
            catch (Exception ex)
            {
                error.AppendLine("Exception thrown: " + ex);
                if (settings.ThrowExceptions) taskCompletionSourceProcess.SetException(ex); else taskCompletionSourceProcess.TrySetResult(false);
            }
            if (success && startInfo.RedirectStandardOutput)
                process.BeginOutputReadLine();
            if (success && startInfo.RedirectStandardError)
                process.BeginErrorReadLine();
            if (success && startInfo.RedirectStandardInput)
            {
                var writeInputTask = Task.Factory.StartNew(() => WriteInputTask());
            }

            async void WriteInputTask()
            {
                var processRunning = true;
                await Task.Delay(50).ConfigureAwait(false);
                try { processRunning = !process.HasExited; } catch { }
                while (processRunning)
                {
                    if (settings.InputText != null)
                    {
                        try
                        {
                            await process.StandardInput.WriteLineAsync(settings.InputText).ConfigureAwait(false);
                            await process.StandardInput.FlushAsync().ConfigureAwait(false);
                            settings.InputText = null;
                        }
                        catch { }
                    }
                    await Task.Delay(5).ConfigureAwait(false);
                    try { processRunning = !process.HasExited; } catch { processRunning = false; }
                }
            }

            if (success && settings.CancellationToken != default(CancellationToken))
                settings.CancellationToken.Register(() => taskCompletionSourceProcess.TrySetResult(true));
            if (success && settings.Timeout_milliseconds > 0)
                new CancellationTokenSource(settings.Timeout_milliseconds).Token.Register(() => taskCompletionSourceProcess.TrySetResult(true));

            var taskProcess = taskCompletionSourceProcess.Task;
            await taskProcess.ConfigureAwait(false);
            if (taskProcess.Result == true) // process was cancelled by token or timeout
            {
                if (!process.HasExited)
                {
                    result.WasCancelled = true;
                    error.AppendLine("Process was cancelled!");
                    try
                    {
                        process.CloseMainWindow();
                        await Task.Delay(30).ConfigureAwait(false);
                        if (!process.HasExited)
                        {
                            process.Kill();
                        }
                    }
                    catch { }
                }
            }
            result.ExitCode = -1;
            if (!settings.DontReadExitCode)     // Reason: sometimes, like when timeout /t 30 is started, reading the ExitCode is only possible if the timeout expired, even if process.Kill was called before.
            {
                try { result.ExitCode = process.ExitCode; }
                catch { output.AppendLine("Reading ExitCode failed."); }
            }
            process.Close();
        }
        finally { var disposeTask = Task.Factory.StartNew(() => process.Dispose()); }    // start in new Task because disposing sometimes waits until the process is finished, for example while executing following command: ping -n 30 -w 1000 127.0.0.1 > nul
        if (result.ExitCode == -1073741510 && !result.WasCancelled)
        {
            error.AppendLine($"Process exited by user!");
        }
        result.WasSuccessful = !result.WasCancelled && result.ExitCode == 0;
        result.Output = output.ToString();
        result.OutputError = error.ToString();
        return result;
    }
}

1

मुझे लगता है कि आपको इसका उपयोग करना चाहिए:

using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace Extensions
{
    public static class ProcessExtensions
    {
        public static async Task<int> WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
        {
            process = process ?? throw new ArgumentNullException(nameof(process));
            process.EnableRaisingEvents = true;

            var completionSource = new TaskCompletionSource<int>();

            process.Exited += (sender, args) =>
            {
                completionSource.TrySetResult(process.ExitCode);
            };
            if (process.HasExited)
            {
                return process.ExitCode;
            }

            using var registration = cancellationToken.Register(
                () => completionSource.TrySetCanceled(cancellationToken));

            return await completionSource.Task.ConfigureAwait(false);
        }
    }
}

उपयोग उदाहरण:

public static async Task<int> StartProcessAsync(ProcessStartInfo info, CancellationToken cancellationToken = default)
{
    path = path ?? throw new ArgumentNullException(nameof(path));
    if (!File.Exists(path))
    {
        throw new ArgumentException(@"File is not exists", nameof(path));
    }

    using var process = Process.Start(info);
    if (process == null)
    {
        throw new InvalidOperationException("Process is null");
    }

    try
    {
        return await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
    }
    catch (OperationCanceledException)
    {
        process.Kill();

        throw;
    }
}

CancellationTokenयदि इसे रद्द करने Killकी प्रक्रिया नहीं है, तो इसे स्वीकार करने का क्या मतलब है?
थियोडोर जूलियास

CancellationTokenमें WaitForExitAsyncविधि की जरूरत है बस एक प्रतीक्षा रद्द या टाइमआउट सेट करने के लिए सक्षम होने के लिए। एक प्रक्रिया को मारना इसमें किया जा सकता है StartProcessAsync: `` `प्रयास {प्रतीक्षा प्रक्रिया ।aitForExitAsync (रद्दीकरण टोकेन); } catch (OperationCanceledException) {process.Kill (); } `` `
कोन्स्टेंटिन एस।

मेरी राय यह है कि जब कोई विधि स्वीकार करती है CancellationToken, तो टोकन रद्द करने से ऑपरेशन रद्द हो जाना चाहिए, न कि प्रतीक्षा रद्द करना। यह वही है जो विधि का कॉलर सामान्य रूप से उम्मीद करेगा। यदि कॉलर केवल प्रतीक्षा को रद्द करना चाहता है, और ऑपरेशन को अभी भी पृष्ठभूमि में चल रहा है, तो बाहरी रूप से करना काफी आसान है ( यहां एक विस्तार विधि है AsCancelableजो बस कर रही है)।
थियोडोर ज़ूलियास

मुझे लगता है कि यह निर्णय कॉलर द्वारा किया जाना चाहिए (विशेष रूप से इस मामले के लिए, क्योंकि यह विधि प्रतीक्षा से शुरू होती है, सामान्य तौर पर मैं आपसे सहमत हूं), जैसा कि नए उपयोग उदाहरण में है।
कॉन्स्टेंटिन एस।

0

Im वास्तव में प्रक्रिया के निपटान के बारे में चिंतित है, बाहर निकलने के लिए इंतजार के बारे में क्या है ?, यह मेरा प्रस्ताव है (पिछले पर आधारित)

public static class ProcessExtensions
{
    public static Task WaitForExitAsync(this Process process)
    {
        var tcs = new TaskCompletionSource<object>();
        process.EnableRaisingEvents = true;
        process.Exited += (s, e) => tcs.TrySetResult(null);
        return process.HasExited ? Task.CompletedTask : tcs.Task;
    }        
}

फिर, इसे इस तरह उपयोग करें:

public static async Task<int> ExecAsync(string command, string args)
{
    ProcessStartInfo psi = new ProcessStartInfo();
    psi.FileName = command;
    psi.Arguments = args;

    using (Process proc = Process.Start(psi))
    {
        await proc.WaitForExitAsync();
        return proc.ExitCode;
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.