5

私は一日中トラブルシューティングを行ってきました。いくつかの調査と多くの試行錯誤を行った後、への呼び出しがprocess.Start()タイマースレッドで機能しないという事実に問題を絞り込むことができたようです。以下のコードは、メイン スレッドで実行されている場合に機能します。まったく同じコードをタイマー コールバックに入れると、ハングします。なんで?タイマーで動作させるにはどうすればよいですか?

private static void RunProcess()
{
    var process = new Process();

    process.StartInfo.FileName = "cmd";
    process.StartInfo.Arguments = "/c exit";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.RedirectStandardInput = true;
    process.StartInfo.RedirectStandardOutput = true;

    process.Start();  // code hangs here, when running on background thread

    process.StandardOutput.ReadToEnd();

    process.WaitForExit();
}

編集

テストとして、これとまったく同じコードを別のラップトップで使用したところ、同じ問題が発生しました。これは、コンソール アプリに貼り付けることができる完全なコードです。process.Start()ハングしますが、終了するキーを押すとすぐprocess.Start()に、プログラムが終了する前に完了します。

private static System.Timers.Timer _timer;
private static readonly object _locker = new object();

static void Main(string[] args)
{
    ProcessTest();

    Console.WriteLine("Press any key to end.");
    Console.ReadKey();
}
private static void ProcessTest()
{
    Initialize();
}
private static void Initialize()
{
    int timerInterval = 2000;
    _timer = new System.Timers.Timer(timerInterval);
    _timer.Elapsed += new ElapsedEventHandler(OnTimerElapsed);
    _timer.Start();
}
private static void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
    if (!Monitor.TryEnter(_locker)) { return; }  // Don't let  multiple threads in here at the same time.
    try
    {
        RunProcess();
    }
    finally
    {
        Monitor.Exit(_locker);
    }
}
private static void RunProcess()
{
    var process = new Process();
    process.StartInfo.FileName = "cmd";
    process.StartInfo.Arguments = "/c exit";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.RedirectStandardInput = true;
    process.StartInfo.RedirectStandardOutput = true;
    process.Start();  // ** HANGS HERE **
    process.StandardOutput.ReadToEnd();
    process.WaitForExit();
}
4

1 に答える 1

9

この問題について重複した質問がたくさんありますが、あなたのケースにぴったり合うものはありません。デバッガーの Debug + Windows + Threads ウィンドウを使用して問題を確認できます。タイマー スレッドを見つけてダブルクリックします。[コール スタック] ウィンドウを見て、次を確認します。

mscorlib.dll!System.Console.InputEncoding.get() + 0x66 bytes    
System.dll!System.Diagnostics.Process.StartWithCreateProcess(System.Diagnostics.ProcessStartInfo startInfo) + 0x7f5 bytes   
System.dll!System.Diagnostics.Process.Start() + 0x88 bytes  
ConsoleApplication70.exe!Program.RunProcess() Line 43 + 0xa bytes   C#
ConsoleApplication70.exe!Program.OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e) Line 28 + 0x5 bytes    C#
    // etc...

スレッドは、Console.InputEncoding プロパティ ゲッターでデッドロックされています。これは、プロセスのリダイレクトされた出力を文字列に変換するために使用する必要があるエンコーディングを把握するために Process クラスによって使用されます。

これは .NET 4.5 に固有のものであり、.NET のサイド バイ サイド バージョンではないため、4.5 がインストールされているマシン上の 4.0 をターゲットとするアプリにも影響します。デッドロックは、メイン スレッドでの Console.ReadKey() メソッド呼び出しによって発生します。これにより、他のスレッドがコンソールをいじるのを防ぐロックが取得されます。これは、Microsoft ソフトウェア全体でかなりグローバルな変更であり、VS2012 によって作成された C/C++ アプリで使用される CRT もこのロックを追加しました。正確な理由は私には明らかではありませんが、プログラムが入力を求めている間、コンソール出力がコンソール入力と混ざらないようにする必要があります。InputEncoding プロパティがそのロックを取得する必要がある正確な理由は、説明するのが少し難しいですが、コンソール入力へのアクセスをシリアル化するパターンに適合します。もちろん、これは多くのプログラマー、特にあなたのようにスレッド化されたコードをテストする小さなテスト アプリを作成するプログラマーにとって大きな驚きです。TDDへの少しの後退。

回避策は少し不快です。TDD に関しては、デッドロックを回避するために Console.ReadKey() の使用を停止する必要があります。実際のプログラムは、AutoResetEvent の WaitOne() メソッドを使用して、ワーカー スレッドが実行を終了したことを認識します。または、CountDownEvent.Wait() など、コードを数回試してみてください。など。


更新: このデッドロック シナリオは、.NET 4.5 のサービス更新で解決されました。マシンで Windows Update を有効にして入手してください。

于 2013-04-25T15:10:58.767 に答える