【问题标题】:Simulate async deadlock in a console application在控制台应用程序中模拟异步死锁
【发布时间】:2016-10-31 13:51:42
【问题描述】:

通常,异步死锁发生在 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" );
    }
}

所以 Deadlock() 应该在正确的上下文中导致死锁。 为了模拟 ASP.NET 上下文,我使用了来自 https://stackoverflow.com/a/31714115/121240DedicatedThreadSynchronisationContext

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

我在调用 Deadlock() 之前设置了上下文:

SynchronizationContext.SetSynchronizationContext( new DedicatedThreadSynchronisationContext() );

我希望代码挂在这一行,因为它应该捕获上下文:

await Task.Delay( 1000 );

但是,它通过就好了,程序运行到最后,并打印“Pressed”。 (虽然程序挂在 DedicatedThreadSynchronisationContext.ThreadWorkerDelegate() 所以它不存在,但我认为这是一个小问题。)

为什么它不产生死锁?模拟死锁的正确方法是什么?

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

编辑

根据 Luaan 的回答,

我使用 DedicatedThreadSynchronisationContext.Send() 而不是创建新线程:

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

它让 Deadlock() 在上下文下运行,因此“等待”捕获相同的上下文,从而发生死锁。

谢谢卢安!

【问题讨论】:

    标签: c# asynchronous async-await


    【解决方案1】:

    因为Deadlock 与您的同步上下文不在同一个线程上运行。

    您需要确保在同步上下文上运行 Deadlock - 仅设置上下文并调用方法并不能确保这一点。

    只需稍加修改即可做到这一点的最简单方法是将Deadlock 同步发送到同步上下文:

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

    这给你一个延迟任务等待的很好的死锁:)

    【讨论】:

    • 有道理。我在 DedicatedThreadSynchronisationContext 上调用了 send(),瞧,死锁!
    【解决方案2】:

    您正在使用的同步上下文是创建一个新线程并将所有工作发送给该线程以完成,因此您阻塞设置同步上下文的线程这一事实与不是线程在做这项工作。

    如果您想使其死锁,您需要安排一个回调以使用该同步上下文,然后在 那里 阻塞,此时还需要额外的回调才能继续。或者,您可以使用在启动它的线程中运行的更传统的消息循环,而不是静默创建一个新循环。

    【讨论】:

      猜你喜欢
      • 2018-08-22
      • 1970-01-01
      • 2021-12-14
      • 2012-01-25
      • 2015-12-19
      • 1970-01-01
      • 2010-11-18
      • 1970-01-01
      • 2013-07-11
      相关资源
      最近更新 更多