終了を待っている間にぶら下がって私のプロセスの理由は何であってもよいですか?
このコードは、多くのアクションは例えばMSBuildのを経由してコードを再コンパイルを開始、おそらく問題は、パワーシェルスクリプトが正常に実行された後も、それはあまりにも多くの出力を生成して終了に待っている間、このコードが立ち往生ということである内部行いPowerShellのスクリプトを起動する必要があります
時々、このコードは、罰金を動作し、時にはそれだけで立ち往生ので、それはちょっと「奇妙」です。
コードハングで:
process.WaitForExit(ProcessTimeOutMiliseconds)。
1-2sec一方のタイムアウトなどにPowerShellスクリプトの実行が19secです。
public static (bool Success, string Logs) ExecuteScript(string path, int ProcessTimeOutMiliseconds, params string[] args)
{
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
using (var outputWaitHandle = new AutoResetEvent(false))
using (var errorWaitHandle = new AutoResetEvent(false))
{
try
{
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "powershell.exe",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
WorkingDirectory = Path.GetDirectoryName(path)
};
if (args.Length > 0)
{
var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
process.StartInfo.Arguments += $" {arguments}";
}
output.AppendLine($"args:'{process.StartInfo.Arguments}'");
process.OutputDataReceived += (sender, e) =>
{
if (e.Data == null)
{
outputWaitHandle.Set();
}
else
{
output.AppendLine(e.Data);
}
};
process.ErrorDataReceived += (sender, e) =>
{
if (e.Data == null)
{
errorWaitHandle.Set();
}
else
{
error.AppendLine(e.Data);
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit(ProcessTimeOutMiliseconds);
var logs = output + Environment.NewLine + error;
return process.ExitCode == 0 ? (true, logs) : (false, logs);
}
}
finally
{
outputWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
}
}
}
脚本:
start-process $args[0] App.csproj -Wait -NoNewWindow
[string]$sourceDirectory = "\bin\Debug\*"
[int]$count = (dir $sourceDirectory | measure).Count;
If ($count -eq 0)
{
exit 1;
}
Else
{
exit 0;
}
どこ
$args[0] = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"
編集します
@隠元の溶液に、私は、再試行はMSビルドアップ絞首刑に実行するための小さなラッパーを追加しました
public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
var current = 0;
int attempts_count = 5;
bool _local_success = false;
string _local_logs = "";
while (attempts_count > 0 && _local_success == false)
{
Console.WriteLine($"Attempt: {++current}");
InternalExecuteScript(path, processTimeOutMilliseconds, out _local_logs, out _local_success, args);
attempts_count--;
}
success = _local_success;
logs = _local_logs;
}
どこInternalExecuteScript
隠元のコードがあります
のがの復習から始めましょう受け入れ答え関連記事インチ
問題は、あなたがStandardOutputおよび/またははStandardErrorをリダイレクトする場合は、内部バッファがいっぱいになることができるということです。どのような順序は、使用、問題がある可能性があります。
- あなたがプロセスStandardOutputを読む前に、出口へのプロセスのために待っていれば、それへの書き込みしようとしてブロックし、そのプロセスは決して終了することができます。
- あなたがReadToEndを使用してStandardOutputから読み取る場合、プロセスはStandardOutputを閉じたことがない場合、あなたのプロセスをブロックすることができます(それが終了したことがない場合、たとえば、またはそれはStandardErrorをへの書き込みをブロックされている場合)。
でも、受け入れ答えは、しかし、特定の場合には、実行の順序と戦います。
EDIT:参照を避ける方法については、以下の回答説明ObjectDisposedExceptionをタイムアウトが発生した場合。
これは、Rxは本当に輝いていることを、あなたはいくつかのイベントを組織したい状況、これらの種類の中にあります。
受信の.NET実装がSystem.Reactive NuGetパッケージとして利用可能であることに注意してください。
Rxはイベントでの作業が容易にどのように参照してみましょうダイビング。
// Subscribe to OutputData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
.Subscribe(
eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);
FromEventPattern
私たちは、統一されたストリーム(別名観測可能)にイベントの明確な発生をマップすることができます。これは、私たちは(LINQのような意味を持つ)パイプラインのイベントを処理することができます。Subscribe
ここで使用される過負荷が設けられているAction<EventPattern<...>>
とAction<Exception>
。観測されたイベントが発生するたびに、そのsender
とがargs
でラップされますEventPattern
と押し通しAction<EventPattern<...>>
。例外がパイプラインで提起された場合、Action<Exception>
使用されています。
欠点の一つEvent
のパターンは、明らかにこのユースケースに示す(および参照ポスト内のすべての回避策によって)、どこのイベントハンドラを解除する際に/それは明らかではないということです。
Rxので、私たちは取り戻すIDisposable
私たちは、サブスクリプションを作成するとき。我々はそれを処分するとき、我々は効果的にサブスクリプションを終了します。追加によりDisposeWith
(から借り拡張メソッドRxUI)、我々は複数追加することができますIDisposable
し、SをCompositeDisposable
(名前のdisposables
コードサンプルで)。私たちはすべて完了したら、我々は1回の呼び出しですべてのサブスクリプションを終了することができますdisposables.Dispose()
。
確かに、我々はバニラ.NETで行うことができないだろうことを、私たちはRxのでできることは何もありません。結果のコードは、あなたが思考の機能的な方法に適応したら、程度の理由だけで多くの方が簡単です。
public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
using (var process = new Process())
using (var disposables = new CompositeDisposable())
{
process.StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "powershell.exe",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
WorkingDirectory = Path.GetDirectoryName(path)
};
if (args.Length > 0)
{
var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
process.StartInfo.Arguments += $" {arguments}";
}
output.AppendLine($"args:'{process.StartInfo.Arguments}'");
// Raise the Process.Exited event when the process terminates.
process.EnableRaisingEvents = true;
// Subscribe to OutputData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
.Subscribe(
eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);
// Subscribe to ErrorData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.ErrorDataReceived))
.Subscribe(
eventPattern => error.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);
var processExited =
// Observable will tick when the process has gracefully exited.
Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
// First two lines to tick true when the process has gracefully exited and false when it has timed out.
.Select(_ => true)
.Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
// Force termination when the process timed out
.Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );
// Subscribe to the Process.Exited event.
processExited
.Subscribe()
.DisposeWith(disposables);
// Start process(ing)
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// Wait for the process to terminate (gracefully or forced)
processExited.Take(1).Wait();
logs = output + Environment.NewLine + error;
success = process.ExitCode == 0;
}
}
我々はすでに我々は肉の部分に直接ジャンプすることができますので、我々は、観測に私たちのイベントをマップする最初の部分を、議論しました。ここでは、私たちに、観察を割り当てprocessExited
、我々は複数回、それを使用したいので、変数。
まず、我々は呼び出すことによって、それを活性化させますSubscribe
。以降、我々は「のawait」は最初の値にしたいときに。
var processExited =
// Observable will tick when the process has gracefully exited.
Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
// First two lines to tick true when the process has gracefully exited and false when it has timed out.
.Select(_ => true)
.Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
// Force termination when the process timed out
.Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );
// Subscribe to the Process.Exited event.
processExited
.Subscribe()
.DisposeWith(disposables);
// Start process(ing)
...
// Wait for the process to terminate (gracefully or forced)
processExited.Take(1).Wait();
OPの問題点の一つは、それが前提としているprocess.WaitForExit(processTimeOutMiliseconds)
ときにタイムアウトするプロセスを終了します。以下からのMSDN:
指示プロセスコンポーネント出口に関連するプロセスのために指定されたミリ秒数を待機します。
タイムアウトには、それは単に現在のスレッドに制御を返すときの代わりに、(すなわち、それは、ブロッキングを停止します)。あなたは、プロセスがタイムアウト手動で強制終了する必要があります。タイムアウトが発生した際に知るために、我々は、マッピングすることができますProcess.Exited
にイベントをprocessExited
処理するために観測可能。我々は、入力を準備することができます。この方法でDo
作業。
コードはかなり自明です。場合はexitedSuccessfully
、プロセスが正常に終了しているだろう。そうでない場合exitedSuccessfully
、終了は強制的にする必要があります。注process.Kill()
非同期に実行され、REF 発言。しかし、呼び出したprocess.WaitForExit()
直後には、再びデッドロックの可能性を開きます。そうであっても強制終了した場合には、それはすべて使い捨ての時にクリーンアップされるようにする方が良いusing
出力が中断/とにかく壊れて考えることができるように、スコープが終了します。
try catch
構築物を、あなたが揃ってきた例外的なケース(しゃれが意図していない)のために予約されてprocessTimeOutMilliseconds
完全にプロセスが必要とする実際の時間で。つまり、競合状態はとの間で発生するProcess.Exited
イベントとタイマー。この出来事の可能性が再びの非同期性によって拡大されますprocess.Kill()
。私は、テスト中に一度遭遇しました。
完全性、DisposeWith
拡張メソッド。
/// <summary>
/// Extension methods associated with the IDisposable interface.
/// </summary>
public static class DisposableExtensions
{
/// <summary>
/// Ensures the provided disposable is disposed with the specified <see cref="CompositeDisposable"/>.
/// </summary>
public static T DisposeWith<T>(this T item, CompositeDisposable compositeDisposable)
where T : IDisposable
{
if (compositeDisposable == null)
{
throw new ArgumentNullException(nameof(compositeDisposable));
}
compositeDisposable.Add(item);
return item;
}
}