2016-10-31 2 views
5

通常、非同期デッドロックはUIスレッドまたはASP.NETコンテキストで発生します。私は私のライブラリコードを単体テストできるようにコンソールアプリケーションでデッドロックをシミュレートしようとしています。コンソールアプリケーションでの非同期デッドロックのシミュレーション

だからここに私の試みです:

class Program 
{ 
    private static async Task DelayAsync() 
    { 
     Console.WriteLine("DelayAsync.Start"); 
     await Task.Delay(1000); 
     Console.WriteLine("DelayAsync.End"); 
    } 

    // This method causes a deadlock when called in a GUI or ASP.NET context. 
    public static void Deadlock() 
    { 
     Console.WriteLine("Deadlock.Start"); 
     // Start the delay. 
     var delayTask = DelayAsync(); 
     // Wait for the delay to complete. 
     delayTask.Wait(); 
     Console.WriteLine("Deadlock.End"); 
    } 

    static void Main(string[] args) 
    { 
     var thread = new Thread(() => 
     { 
      Console.WriteLine("Thread.Start"); 
      SynchronizationContext.SetSynchronizationContext(new DedicatedThreadSynchronisationContext()); 
      Deadlock(); 
      Console.WriteLine("Thread.End"); 
     }); 
     thread.Start(); 
     Console.WriteLine("Thread.Join.Start"); 
     thread.Join(); 
     Console.WriteLine("Thread.Join.End"); 
     Console.WriteLine("Press any key to exit"); 
     Console.ReadKey(true); 
     Console.WriteLine("Pressed"); 
    } 
} 

のでデッドロック()は右文脈でデッドロックが発生する必要があります。

SynchronizationContext.SetSynchronizationContext(new DedicatedThreadSynchronisationContext()); 

が、私はコードがハングアップすることを期待:私はデッドロック()を呼び出す前にコンテキストを設定

public sealed class DedicatedThreadSynchronisationContext : SynchronizationContext, IDisposable 
{ 
    public DedicatedThreadSynchronisationContext() 
    { 
     m_thread = new Thread(ThreadWorkerDelegate); 
     m_thread.Start(this); 
    } 

    public void Dispose() 
    { 
     m_queue.CompleteAdding(); 
    } 

    /// <summary>Dispatches an asynchronous message to the synchronization context.</summary> 
    /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param> 
    /// <param name="state">The object passed to the delegate.</param> 
    public override void Post(SendOrPostCallback d, object state) 
    { 
     if (d == null) throw new ArgumentNullException("d"); 
     m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); 
    } 

    /// <summary> As 
    public override void Send(SendOrPostCallback d, object state) 
    { 
     using (var handledEvent = new ManualResetEvent(false)) 
     { 
      Post(SendOrPostCallback_BlockingWrapper, Tuple.Create(d, state, handledEvent)); 
      handledEvent.WaitOne(); 
     } 
    } 

    public int WorkerThreadId { get { return m_thread.ManagedThreadId; } } 
    //========================================================================================= 

    private static void SendOrPostCallback_BlockingWrapper(object state) 
    { 
     var innerCallback = (state as Tuple<SendOrPostCallback, object, ManualResetEvent>); 
     try 
     { 
      innerCallback.Item1(innerCallback.Item2); 
     } 
     finally 
     { 
      innerCallback.Item3.Set(); 
     } 
    } 

    /// <summary>The queue of work items.</summary> 
    private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue = 
     new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); 

    private readonly Thread m_thread = null; 

    /// <summary>Runs an loop to process all queued work items.</summary> 
    private void ThreadWorkerDelegate(object obj) 
    { 
     SynchronizationContext.SetSynchronizationContext(obj as SynchronizationContext); 

     try 
     { 
      foreach (var workItem in m_queue.GetConsumingEnumerable()) 
       workItem.Key(workItem.Value); 
     } 
     catch (ObjectDisposedException) { } 
    } 
} 

: は、私はDedicatedThreadSynchronisationContext https://stackoverflow.com/a/31714115/121240からを使用しています、ASP.NETのコンテキストをシミュレートするために、この行はコンテキストをキャプチャする必要があるためです。

await Task.Delay(1000); 

ただし、 dプログラムは最後まで実行され、 "Pressed"が印刷されます。 (プログラムはDedicatedThreadSynchronisationContext.ThreadWorkerDelegate()にハングアップしますが、存在しませんが、小さな問題だと思います。)

デッドロックが発生しないのはなぜですか?デッドロックをシミュレートする適切な方法は何ですか?

========================================

EDIT

Luaanによって答えを1として

私は(DedicatedThreadSynchronisationContext.Sendを使用)の代わりに、新しいスレッドを作成する:

 Console.WriteLine("Send.Start"); 
     var staContext = new DedicatedThreadSynchronisationContext(); 
     staContext.Send((state) => 
     { 
      Deadlock(); 
     }, null); 
     Console.WriteLine("Send.End"); 

それは(デッドロックをすることができます)コンテキストで実行するので、「待ちます」同じコンテキストを捕捉し、デッドロックが発生します。

ありがとうございました!

答えて

3

Deadlockは、同期コンテキストと同じスレッドで実行されないためです。

同期コンテキストでDeadlockを実行する必要があります。コンテキストを設定してメソッドを呼び出すだけでは、それを保証するものではありません。

少しの変更でこれを行う最も簡単な方法は、同期コンテキストに同期しDeadlockを送信することです:

SynchronizationContext.Current.Send(_ => Deadlock(), null); 

これはあなたに遅延タスク待ちの素敵なデッドロック:)

+0

を与えることは理にかなっています。 DedicatedThreadSynchronisationContextでsend()を呼び出し、デッドロックであるvoilaを呼び出しました。 – wooohoh

0

使用している同期コンテキストは、新しいスレッドを作成し、そのスレッドに送信されたすべての作業を完了することです。同期コンテキストを設定したスレッドをブロックするという事実は無関係ですそれは仕事をしているスレッドではないからです。

デッドロックしたい場合は、その同期コンテキストを使用するようにコールバックをスケジュールしてから、をブロックする必要があります。この場合、コールバックを続行する必要があります。あるいは、新しいスレッドを静かに作成するのではなく、スレッドを起動するスレッドで実行するより伝統的なメッセージループを使用することもできます。

関連する問題