An asynchronous method in a C # class that executes a process

I have the following question for this post . In my version, I have the following that I want to make asynchronous. Here is what I have:

public virtual Task<bool> ExecuteAsync() { var tcs = new TaskCompletionSource<bool>(); string exe = Spec.GetExecutablePath(); string args = string.Format("--input1={0} --input2={1}", Input1, Input2); try { var process = new Process { EnableRaisingEvents = true, StartInfo = { UseShellExecute = false, FileName = exe, Arguments = args, RedirectStandardOutput = true, RedirectStandardError = true, WorkingDir = CaseDir } }; process.Exited += (sender, arguments) => { if (process.ExitCode != 0) { string errorMessage = process.StandardError.ReadToEndAsync(); tcs.SetResult(false); tcs.SetException(new InvalidOperationException("The process did not exit correctly. Error message: " + errorMessage)); } else { File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd()); tcs.SetResult(true); } process.Dispose(); }; process.Start(); } catch (Exception e) { Logger.InfoOutputWindow(e.Message); tcs.SetResult(false); return tcs.Task; } return tcs.Task; } } 

Here Spec, Input1, Input2, CaseDir, LogFile are all members of the class that ExecuteAsync is a method. Is it normal to use them like that? The parts I'm struggling with are the following:

  • I cannot use the async keyword when defining a method ( public virtual async Task<bool> ExecuteAsync() ) without warning that I need the await keyword, while I have one in the lambda expression for the process. Do I even need the async keyword when defining a method? I saw supposedly asynchronous examples where they do not use it, for example. this one . If I take it out, it compiles, but can I use it asynchronously?
  • Is using the async keyword in a lambda expression and the corresponding await process.StandardError.ReadToEndAsync() OK inside the process lambda expression? In this example, they do not use async await on the corresponding line, so I wonder how they got away from it? Did this leave it blocking since I was told that the ReadToEnd method ReadToEnd blocking?
  • Does my call to File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd()) allow the whole method to be locked? If so, how can I avoid this, if at all possible?
  • Does exception handling make sense? Should I be aware of any information about the logger method Logger.InfoOutputWindow that I used in the catch ?
  • Finally, why does the process.Exited event always appear before process.Start() in all the examples I came across? Is it possible to put process.Start() before the process.Exited event?

Appreciate any ideas and in advance for your attention and attention.

EDIT. # one:

For # 3 above, I had an idea based in part on a comment from @ René Vogt below, so I made a change to move the call to File.WriteAllText(...) inside the else {} block of the process.Exited event. Perhaps these are addresses number 3.

EDIT # 2:

I made the initial list of changes (now the code snippet has been changed), basically deleted both the async in the function definition and the await keyword in the process.Exited event handler based on the original comments from @ René Vogt, I have not tried my last changes below . When I run, I get an exception:

 A plugin has triggered error: System.InvalidOperationException; An attempt was made to transition a task to a final state when it had already completed. 

The application log has a call stack as follows:

 UNHANDLED EXCEPTION: Exception Type: CLR Exception (v4) Exception Details: No message (.net exception object not captured) Exception Handler: Unhandled exception filter Exception Thread: Unnamed thread (id 29560) Report Number: 0 Report ID: {d80f5824-ab11-4626-930a-7bb57ab22a87} Native stack: KERNELBASE.dll+0x1A06D RaiseException+0x3D clr.dll+0x155294 clr.dll+0x15508E <unknown/managed> (0x000007FE99B92E24) <unknown/managed> (0x000000001AC86B00) Managed stack: at System.Threading.Tasks.TaskCompletionSource`1.SetException(Exception exception) at <namespace>.<MyClass>.<>c__DisplayClass3.<ExecuteAsync>b__2(Object sender, EventArgs arguments) at System.Diagnostics.Process.RaiseOnExited() at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading._ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Object state, Boolean timedOut) 
+1
c # lambda asynchronous process task
May 20 '16 at 2:56
source share
1 answer
  • You do not need async in your method signature because you are not using await . This is enough to return a Task . The caller may await that Task - or not, this has nothing to do with your method.

  • Do not use the async on this lambda and do not use the asynchronous ReadToEnd inside this lambda. It is difficult to predict what will happen if you return from this event handler before it completes. And anyway you want to finish this method. He called when the process exited, there is no need to do this async .

  • Here it is the same as in (2). I think it’s ok to do this “synchronously” inside this event handler. It only blocks this handler, but the handler is called after the process exits, so I think this is normal for you.

  • Your exception handling looks fine, but I would add another try/catch inside the Exited event Exited . But this is not based on knowledge, but on the experience that everywhere something can go wrong :)




To get the standard and erroneous output, I suggest subscribing to the ErrorDataReceived and OutputDataReceived events and OutputDataReceived StringBuilder received data.

In your method, declare two StringBuilders :

 StringBuilder outputBuilder = new StringBuilder(); StringBuilder errorBuilder = new StringBuilder(); 

And subscribe to events immediately after creating the process instance:

 process.OutputDataReceived += (sender, e) => outputBuilder.AppendLine(e.Data); process.ErrorDataReceived += (sender, e) => errorBuilder.AppendLine(e.Data); 

Then you only need to call these two methods immediately after , you called process.Start() (it will not work earlier because stdout and stderr are not open yet):

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

In your Exited event Exited you can call outputBuilder.ToString() (or errorBuilder.ToString() respectively) instead of ReadToEnd , and everything should work fine.

Unfortunately, there is a drawback: if the process is very fast, your Exited handler Exited theoretically be called before Begin*ReadLine calls. I don’t know how to handle this, but it is unlikely to happen.

+3
May 20, '16 at 15:28
source share




All Articles