模拟控制台应用程序中的异步死锁
本文关键字:异步 死锁 控制台 应用程序 模拟 | 更新日期: 2023-09-27 18:15:50
通常,异步死锁发生在UI线程或ASP线程中。净上下文。我正在尝试模拟控制台应用程序中的死锁,以便我可以对我的库代码进行单元测试。
这是我的尝试:
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上下文中,我使用DedicatedThreadSynchronisationContext从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()之前设置了上下文:
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()在上下文中运行,因此'await'捕获相同的上下文中,从而发生死锁。
谢谢你,罗安!
标题>因为Deadlock
没有运行在与同步上下文相同的线程上。
您需要确保在同步上下文中运行Deadlock
—仅仅设置上下文和调用方法并不能确保这一点。
Deadlock
同步发送到同步上下文:
SynchronizationContext.Current.Send(_ => Deadlock(), null);
这给了你一个很好的延迟任务等待死锁:)
您正在使用的同步上下文正在创建一个新线程,并将所有工作发送给该线程来完成,因此您阻塞您设置同步上下文的线程的事实是无关的,因为它不是正在做工作的线程。
如果你想让它死锁,你需要安排一个回调来使用同步上下文,然后在那里阻塞,当还需要额外的回调来继续它时。或者,您可以使用在启动它的线程中运行的更传统的消息循环,而不是静默地创建一个新的消息循环。