在控制台应用程序中模拟异步死锁 [英] Simulate async deadlock in a console application

查看:66
本文介绍了在控制台应用程序中模拟异步死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

通常,异步死锁发生在UI线程或ASP.NET上下文中.我正在尝试在控制台应用程序中模拟死锁,以便可以对库代码进行单元测试.

Usually, an async deadlock occurs in UI thread or with ASP.NET context. I'm trying to simulate the deadlock in a console application so that I can unit test my library codes.

这是我的尝试:

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的 DedicatedThreadSynchronisationContext /31714115/121240 :

So Deadlock() should cause a deadlock in an right context. To simulate the ASP.NET context, I'm using DedicatedThreadSynchronisationContext from https://stackoverflow.com/a/31714115/121240:

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()之前设置了上下文:

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

但是,它通过得很好,并且程序运行到最后,并显示"Pressed".(尽管该程序挂在DedicatedThreadSynchronisationContext.ThreadWorkerDelegate()上,所以它不存在,但我认为这是一个小问题.)

However , it passes just fine and the program runs through the end, and it prints "Pressed". (Although the program hangs on DedicatedThreadSynchronisationContext.ThreadWorkerDelegate() so it doesn't exist, but I consider it a minor issue.)

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

Why doesn't it produce a dead lock? What is the proper way to simulate a dead lock?

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

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

根据Luaan的回答,

As per the answer by Luaan,

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

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

它使Deadlock()在上下文中运行,因此'await'捕获了相同的上下文,因此发生了死锁.

It lets Deadlock() to run under the context, so 'await' captures the same context thus a dead lock occurs.

谢谢Lu!

推荐答案

因为死锁与同步上下文不在同一线程上运行.

Because Deadlock doesn't run on the same thread as your synchronization context.

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

You need to make sure to run Deadlock on the synchronization context - just setting the context and calling a method doesn't ensure that.

最简单的修改方法是将 Deadlock 同步发送到同步上下文:

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

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

这给您一个延迟任务等待的不错的僵局:)

This gives you a nice deadlock on the delay task wait :)

这篇关于在控制台应用程序中模拟异步死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆