2016-05-20 16 views
1

this postへのフォローアップの質問があります。私のバージョンでは、次のことを非同期にしたいと思っています。ここで私が持っているものである:ここではプロセスを実行するC#クラスの非同期メソッド

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; 
    } 
} 

Spec, Input1, Input2, CaseDir, LogFileはExecuteAsyncがメソッドであるクラスのすべてのメンバーです。そういうふうに使ってもいいですか?私は苦労していますパーツは以下のとおりです。

    私はラムダ式で1を持っているのに対し、私は awaitキーワードを必要とする警告なしにメソッド定義( public virtual async Task<bool> ExecuteAsync())で非同期キーワードを使用するように見えることはできません
  1. プロセスのために。メソッド定義でasyncキーワードが必要なのでしょうか?おそらく非同期的な例を見てきました。 this one。私はそれを取り出してコンパイルしますが、私はこれを非同期で使用できますか?
  2. ラムダ式でasyncキーワードを使用していて、プロセスのラムダ式内の対応するawait process.StandardError.ReadToEndAsync() OKですか? this exampleでは、対応する行にasync awaitを使用していないので、どうやってそれを取り除いたのだろうか?方法ReadToEndがブロックされていると言われて以来、ブロックされていないまま放置しないでください。
  3. 私はFile.WriteAllText(LogFile, process.StandardOutput.ReadToEnd())のメソッド呼び出しをブロックすることになっていますか?もしそうなら、どうすればそれを避けることができますか?
  4. 例外処理は意味がありますか? catchブロックで使用したアプリケーションログツールLogger.InfoOutputWindowの詳細を知っておく必要がありますか?
  5. 最後に、process.Exitedイベントは、私が出会ったすべての例ではいつもprocess.Start()の前に表示されますか? process.Exitedイベントの前にprocess.Start()を置くことはできますか?

あなたの興味のために、どんなアイデアや感謝をお待ちしております。&注目。

EDIT#1:上記の#3のために、私は以下のルネ・フォークト@からのコメントに一部基づいてアイデアを持っていたので、私はのelse {}ブロック内File.WriteAllText(...)コールを移動するには、変更を行った

process.Exitedイベント。おそらく、これは#3を扱うでしょう。

EDIT#2:

Iは、元に基づいて変更(コードスニペットは、現在変更されている)、基本的に除去機能定義でasyncキーワードとprocess.Exitedイベントハンドラでawaitキーワードの両方の初期リストを作っ@RenéVogtのコメントまだ最近の彼の最近の変更を試していない。私は実行すると、私は例外を取得:次のように

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

は、アプリケーションログには、コールスタックを持っています

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) 
+0

この質問は非常に広いようです。同じ質問に5つの異なる質問がありますか?一度に1つのパーツのヘルプを実装、デバッグ、検索する必要があります。あなたは次のものに移動する前に、その部分を働かせることを確認してください。 –

+1

私はRenéに 'ReadToEndAsync()'を呼び出すべきではないことに同意しますが、 'RedirectStandardError'や' RedirectStandardOutput'を決して設定しないで、stdoutストリームとstderrストリームを読み込むためにプロセスが終了するまで待ってくださいプロセスをデッドロックする良い方法です(これは古典的な間違いです:出力ストリームバッファがいっぱいになる、プロセスブロック、コード、プロセスが_exits_のときだけ実行する、ストリームを読み込んでバッファを空にしない) –

答えて

3
  1. ので、あなたはドンあなたは、あなたのメソッドのシグネチャにasyncを必要としませんawaitを使用してください。 Taskを返すだけで十分です。呼び出し元はawaitですTask - それはあなたの方法とは関係ありません。

  2. このラムダにasyncキーワードを使用しないでください。そのラムダの中に非同期ReadToEndを使用しないでください。実際に終了する前にそのイベントハンドラから戻った場合に何が起こるかを予測するのは難しいです。そして、とにかくその方法を終わらせたいと思っています。プロセスが終了したときに呼び出されます。これを行う必要はありません。async

  3. ここでは(2)と同じです。私は、このイベントハンドラ内でこれを "同期的に"実行することは大丈夫だと思います。このハンドラをブロックするだけですが、プロセスが終了した後にハンドラが呼び出されるので、あなたにとっては大丈夫です。

  4. 例外処理は正常ですが、別のtry/catchブロックをExitedイベントハンドラ内に追加します。しかし、それは知識で、むしろどこでも何かが間違って行くことができます:)


を標準エラー出力を得るためのより良い方法についての経験に基づいていない、私はErrorDataReceivedOutputDataReceivedイベントにサブスクライブすることをお勧め受け取ったデータでStringBuilderを入力します。あなたの方法で

、2 StringBuildersを宣言:

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

そして、あなたはprocessをインスタンス化した直後のイベントをサブスクライブ:

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

を次にあなたが唯一の右後にこれらの2つの方法を呼び出す必要がありますprocess.Start()と呼ばれています(stdoutとstderrはまだ開かれていないため、以前は動作しません)。

あなたのExitedイベントハンドラで
process.Start(); 
process.BeginErrorReadLine(); 
process.BeginOutputReadLine(); 

あなたは、代わりにReadToEndの(それぞれまたはerrorBuilder.ToString()outputBuilder.ToString()を呼び出すことができ、すべてが正常に動作する必要があります。

残念ながら、プロセスが非常に速い場合は、ハンドラが理論的に呼び出されてからBegin*ReadLineが呼び出されることがあります。それをどう扱うべきかは分かりませんが、それは起こりそうもありません。

+0

Thanks @RenéVogt、 (私はテストする前にいくつかの変更を加えなければならないので、私にそれをご負担ください)、必要に応じて修正を加えて答えをマークしてください。 –

+0

@ squashed.bugabooはあなたの時間を取るが、週末は休み、月曜日(UTC + 1)前にあなたに戻る時間があるかどうかわからないが、試してみる。 –

+0

@スカッシュ。bugaboo 'ReadToEnd'の問題を回避するプロセスから標準/エラー出力を読み取る別の方法を追加しました –

関連する問題