2017-12-20 37 views
5

私はデッドロック問題に直面しています。ありがたいことに、私は以下の例で問題を再現できました。通常の.Net Core 2.0コンソールアプリケーションとして実行します。私が期待するものデッドロックの原因は何ですか?

class Class2 
{ 

    static void Main(string[] args) 
    { 
     Task.Run(MainAsync); 
     Console.WriteLine("Press any key..."); 
     Console.ReadKey(); 
    } 

    static async Task MainAsync() 
    { 
     await StartAsync(); 
     //await Task.Delay(1); //a little delay makes it working 
     Stop(); 
    } 


    static async Task StartAsync() 
    { 
     var tcs = new TaskCompletionSource<object>(); 
     StartCore(tcs); 
     await tcs.Task; 
    } 


    static void StartCore(TaskCompletionSource<object> tcs) 
    { 
     _cts = new CancellationTokenSource(); 
     _thread = new Thread(Worker); 
     _thread.Start(tcs); 
    } 


    static Thread _thread; 
    static CancellationTokenSource _cts; 


    static void Worker(object state) 
    { 
     Console.WriteLine("entering worker"); 
     Thread.Sleep(100); //some work 

     var tcs = (TaskCompletionSource<object>)state; 
     tcs.SetResult(null); 

     Console.WriteLine("entering loop"); 
     while (_cts.IsCancellationRequested == false) 
     { 
      Thread.Sleep(100); //some work 
     } 
     Console.WriteLine("exiting worker"); 
    } 


    static void Stop() 
    { 
     Console.WriteLine("entering stop"); 
     _cts.Cancel(); 
     _thread.Join(); 
     Console.WriteLine("exiting stop"); 
    } 

} 

次のように完全な配列である。

Press any key... 
entering worker 
entering loop 
entering stop 
exiting worker 
exiting stop 

はしかし、実際の配列はThread.Joinコールに屋台:

最後に
Press any key... 
entering worker 
entering stop 

、私は挿入した場合MainAsyncボディの小さな遅延、すべてがうまくいく。 なぜデッドロックが発生するのですか?

注:元のコードではTaskCompletionSourceの代わりにSemaphoreSlimを使用して解決しましたが、問題はまったくありません。私は問題がどこにあるのか理解したいだけです。

+1

「タスク」と「スレッド」を混在させる理由は何ですか? – Groo

+0

'var tcs = new TaskCompletionSource (TaskCreationOptions.RunContinuationsAsynchronously);'に変更し、期待通りに動作します。今度は 'SetResult'は継続を同期的に実行し、' Stop() 'の呼び出しを継続します。これは' SetResult'が実行されたスレッドを結合してデッドロックします。 – Evk

+0

@Grooオリジナルのコードには、タスクを混在させずにいくつかのジョブを実行する長時間実行スレッド(ワーカー)があります。しかし、最初の仕事が完了したときに完了するワーカー/スレッドを開始するために待たれている機能があります。 –

答えて

3

tcs.SetResult(null);Worker()の呼び出しは、基礎となるタスクが完了するまで戻りません(詳細はthis questionをチェックしてください)。タスクのステータスをケースでは、あなたがデッドロックを得る理由ですそのWaitingForActivationです:

  1. tcs.SetResult(null)呼び出しによってブロックされているWorker()を実行するスレッド。

  2. スレッド実行Stop()は、_thread.Join()コールによってブロックされます。

+0

助けてくれてありがとう! –

0

MainAsync()スレッドは他のスレッドよりも「高速」です。 そして、あなたはスレッドではないタスクでのみ制御を行っています

MainAsync()の方法では、StartAsync()メソッドを待って作業を完了してからスレッドを開始します。方法StartAsync()がその作業(作成され、開始されたスレッド)で完了すると、その機能は作業を終了することをMainAsync()に通知します。その後、MainAsync()はStopメソッドを呼び出します。 あなたのスレッドはどこですか?これは、制御なしに並行して実行され、作業を終了しようとします。これはデッドロックではなく、タスクとスレッドの同期はありません。

await Task.Delay(1)は、スレッドがタスクを終了する前に完了するのに十分な速さがあるため、コードが動作する理由は何ですか(thread.join)。

+0

hmm ....まず、StartAsyncは、SetResultメソッドによってTaskCompletionSourceが解放され、スレッド内にあるときに終了します。第2に、トークンがキャンセルされたとマークされない限り(そして、それはStopメソッドにあります)、スレッドは終了しません。 –

関連する問題