Multiple processes in method executed asynchronous

2019-09-16 07:06发布

问题:

Suppose I have multiple (say, two) processes that I want to run sequentially but asynchronously, how do I go about doing this? See snippet below:

public virtual Task<bool> ExecuteAsync()
{
    var tcs = new TaskCompletionSource<bool>();
    string exe1 = Spec.GetExecutablePath1();
    string exe2 = Spec.GetExecutablePath2();
    string args1 = string.Format("--input1={0} --input2={1}", Input1, Input2);
    string args2 = string.Format("--input1={0} --input2={1}", Input1, Input2);

    try
    {
        var process1 = new Process
        {
            EnableRaisingEvents = true,
            StartInfo =
            {
                UseShellExecute = false,
                FileName = exe1,
                Arguments = args1,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                WorkingDir = CaseDir
            }
        };
        var process2 = new Process
        {
            EnableRaisingEvents = true,
            StartInfo =
            {
                UseShellExecute = false,
                FileName = exe2,
                Arguments = args2,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                WorkingDir = CaseDir
            }
        };
        process1.Exited += (sender, arguments) =>
        {
            if (process1.ExitCode != 0)
            {
                string errorMessage = process1.StandardError.ReadToEndAsync();
                tcs.SetResult(false);
                tcs.SetException(new InvalidOperationException("The process1 did not exit correctly. Error message: " + errorMessage));
            }
            else
            {
                File.WriteAllText(LogFile, process1.StandardOutput.ReadToEnd());
                tcs.SetResult(true);
            }
            process1.Dispose();
        };
        process1.Start();

        process2.Exited += (sender, arguments) =>
        {
            if (process2.ExitCode != 0)
            {
                string errorMessage = process2.StandardError.ReadToEndAsync();
                tcs.SetResult(false);
                tcs.SetException(new InvalidOperationException("The process2 did not exit correctly. Error message: " + errorMessage));
            }
            else
            {
                File.WriteAllText(LogFile, process2.StandardOutput.ReadToEnd());
                tcs.SetResult(true);
            }
            process2.Dispose();
        };
        process2.Start();
    }
    catch (Exception e)
    {
        Logger.InfoOutputWindow(e.Message);
        tcs.SetResult(false);
        return tcs.Task;
    }

    return tcs.Task;
}

}

Thanks in advance for your ideas.

EDIT #1:

When I run the code as shown above it fails with the error below after successfully completing the first process:

"System.InvalidOperationException; An attempt was made to transition a task to a final state when it had already completed".

When I run the code with just the first process (delete all code pertaining to the other), it runs OK.

回答1:

The error you're seeing is unrelated to the two processes. It's caused by setting the SetResult method of the TaskCompletionSource multiple times. You can't give the same task two different results.

There are a few important changes:

  1. Don't set the task completion to true just because the first process succeeds. You want it to wait until the second process is done to decide if it's really a success. Remove the call to 'tsc.SetResult(true)' in process1.Exited.
  2. You should only start process2 after process1 is complete (you said you wanted them to be synchronous. To do that, start process2 in process1's Exited handler. Also, I assume you don't want to start process2 if process1 fails
  3. Move the process2.Exited handler to up above where you call process1.Start(). This just avoids a race condition when process1 and process2 complete really quickly before the Exited handler for process2 is set up.

This is untested, but your code should end up looking something like this:

public virtual Task<bool> ExecuteAsync()
{
    var tcs = new TaskCompletionSource<bool>();
    string exe1 = Spec.GetExecutablePath1();
    string exe2 = Spec.GetExecutablePath2();
    string args1 = string.Format("--input1={0} --input2={1}", Input1, Input2);
    string args2 = string.Format("--input1={0} --input2={1}", Input1, Input2);

    try
    {
        var process1 = new Process
        {
            EnableRaisingEvents = true,
            StartInfo =
            {
                UseShellExecute = false,
                FileName = exe1,
                Arguments = args1,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                WorkingDir = CaseDir
            }
        };
        var process2 = new Process
        {
            EnableRaisingEvents = true,
            StartInfo =
            {
                UseShellExecute = false,
                FileName = exe2,
                Arguments = args2,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                WorkingDir = CaseDir
            }
        };
        process1.Exited += (sender, arguments) =>
        {
            if (process1.ExitCode != 0)
            {
                string errorMessage = process1.StandardError.ReadToEndAsync();
                tcs.SetResult(false);
                tcs.SetException(new InvalidOperationException("The process1 did not exit correctly. Error message: " + errorMessage));
            }
            else
            {
                File.WriteAllText(LogFile, process1.StandardOutput.ReadToEnd());
                process2.Start();
            }
            process1.Dispose();
        };

        process2.Exited += (sender, arguments) =>
        {
            if (process2.ExitCode != 0)
            {
                string errorMessage = process2.StandardError.ReadToEndAsync();
                tcs.SetResult(false);
                tcs.SetException(new InvalidOperationException("The process2 did not exit correctly. Error message: " + errorMessage));
            }
            else
            {
                File.WriteAllText(LogFile, process2.StandardOutput.ReadToEnd());
                tcs.SetResult(true);
            }
            process2.Dispose();
        };

        process1.Start();
    }
    catch (Exception e)
    {
        Logger.InfoOutputWindow(e.Message);
        tcs.SetResult(false);
        return tcs.Task;
    }

    return tcs.Task;
}