Simulate an asynchronous deadlock in a console application

Typically, an asynchronous deadlock occurs in a user interface thread or in an ASP.NET context. I am trying to simulate a dead end in a console application so that I can unit test my library codes.

So here is my attempt:

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

So Deadlock () should cause a deadlock in the right context. To simulate an ASP.NET context, I use the DedicatedThreadSynchronisationContext from https://stackoverflow.com/a/167269/28 :

 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 ) { } } } 

I set the context before calling Deadlock ():

 SynchronizationContext.SetSynchronizationContext( new DedicatedThreadSynchronisationContext() ); 

I expect the code to hang on this line because it should capture the context:

 await Task.Delay( 1000 ); 

However, it runs very well, and the program runs to the end, and it prints "Pressed". (Although the program hangs on DedicatedThreadSynchronisationContext.ThreadWorkerDelegate (), so it does not exist, but I consider it a minor problem.)

Why doesn't he create a deadlock? What is the correct way to simulate deadlock?

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

EDIT

According to Luaan’s answer,

I used DedicatedThreadSynchronisationContext.Send () instead of creating a new thread:

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

It allows Deadlock () to be launched under the context, so the β€œwait” captures the same context, thus deadlocking occurs.

Thanks Luaan!

+5
source share
2 answers

Because Deadlock does not work in the same thread as your sync context.

You need to make sure that you start Deadlock in the context of synchronization - just setting the context and calling the method do not guarantee this.

The easiest way to do this with a little modification is to send Deadlock synchronously to the synchronization context:

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

This gives you a great dead end while waiting for a wait task :)

+3
source

The synchronization context that you use creates a new thread and does all the work sent to it in that thread, so the fact that you are blocking the thread specified for the synchronization context does not matter, because it is not the thread that does the work.

If you want to get stuck, you need to schedule a callback to use this synchronization context, and then block it when there are additional callbacks needed to continue. Alternatively, you can use a more traditional message loop that starts in a thread that starts it, instead of creating new text silently.

0
source

Source: https://habr.com/ru/post/1259042/


All Articles