Winform同步调用异步函数死锁原因分析、为什么要用异步 (3)

image-20211016203612036

image-20211016204719967

执行过程:

可以看到9和10都在UI线程执行,但是UI线程已经被10的执行流程占用,导致9无法将任务设置为完成状态,陷入死锁。

image-20211017114845175

编译后的DeadTask函数

由于编译的代码不清晰,我进行重命名和代码精简。

可以看到DeadTask返回DeadTaskAsyncStateMachine.Task,看来要整明白AsyncTaskMethodBuilder执行过程,才能清楚来龙去脉了。

private Task<string> DeadTask() { DeadTaskAsyncStateMachine stateMachine = new DeadTaskAsyncStateMachine(); stateMachine.tBuilder = AsyncTaskMethodBuilder<string>.Create(); stateMachine.form1 = this; stateMachine.state1 = -1; stateMachine.tBuilder.Start(ref stateMachine); return stateMachine.tBuilder.Task; } 编译生成的DeadTaskAsyncStateMachine类

由于编译的代码不清晰,我进行重命名。

private sealed class DeadTaskAsyncStateMachine : IAsyncStateMachine { public int state1; public AsyncTaskMethodBuilder<string> tBuilder; public Form1 form1; private string taskResult; private TaskAwaiter delay500Awaiter; private TaskAwaiter<string> helloWorldAwaiter; private void MoveNext() { int num = state1; string finalResult; try { TaskAwaiter<string> awaiter; TaskAwaiter awaiter2; if (num != 0) { if (num == 1) { awaiter = helloWorldAwaiter; helloWorldAwaiter = default(TaskAwaiter<string>); num = (state1 = -1); goto finalTag; } awaiter2 = Task.Delay(500).GetAwaiter(); if (!awaiter2.IsCompleted) { num = (state1 = 0); delay500Awaiter = awaiter2; DeadTaskAsyncStateMachine stateMachine = this; tBuilder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine); return; } } else { awaiter2 = delay500Awaiter; delay500Awaiter = default(TaskAwaiter); num = (state1 = -1); } awaiter2.GetResult(); awaiter = Task.FromResult("Hello world").GetAwaiter(); // 因为awaiter.IsCompleted == true,部分代码进行移除 goto finalTag; finalTag: finalResult = awaiter.GetResult(); } catch (Exception exception) { state1 = -2; tBuilder.SetException(exception); return; } state1 = -2; tBuilder.SetResult(finalResult); // 设置结果,同时设置任务为完成状态 } void IAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); // 执行状态机当前任务,初始状态state1 = -1 } private void SetStateMachine(IAsyncStateMachine stateMachine) { } void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } } 关键代码: MoveNext源码

image-20211016224555545

AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted源码:

可以看到将会调用函数TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true)。

public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => AsyncMethodBuilderCore.Start(ref stateMachine); //源码地址: //https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs#L101 internal static void AwaitUnsafeOnCompleted<TAwaiter>( ref TAwaiter awaiter, IAsyncStateMachineBox box) where TAwaiter : ICriticalNotifyCompletion { // 执行位置,默认continueOnCapturedContext = true即为继续在上下文执行 // 最终SynchronizationContext.Current.Post触发执行stateMachine.MoveNext if ((null != (object?)default(TAwaiter)) && (awaiter is ITaskAwaiter)) { ref TaskAwaiter ta = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter); // relies on TaskAwaiter/TaskAwaiter<T> having the same layout TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true); } // ConfigureAwait(false).GetAwaiter()返回类型为IConfiguredTaskAwaiter,可以避免死锁 else if ((null != (object?)default(TAwaiter)) && (awaiter is IConfiguredTaskAwaiter)) { ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter); TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext); } // 省略代码未知行 } AsyncMethodBuilderCore.Start源码: //源码地址 //https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs#L21 public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine); } // enregistrer variables with 0 post-fix so they can be used in registers without EH forcing them to stack // Capture references to Thread Contexts Thread currentThread0 = Thread.CurrentThread; Thread currentThread = currentThread0; ExecutionContext? previousExecutionCtx0 = currentThread0._executionContext; ExecutionContext? previousExecutionCtx = previousExecutionCtx0; SynchronizationContext? previousSyncCtx = currentThread0._synchronizationContext; try { // 执行DeadTaskAsyncStateMachine.MoveNext() stateMachine.MoveNext(); } finally { // Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack SynchronizationContext? previousSyncCtx1 = previousSyncCtx; Thread currentThread1 = currentThread; // The common case is that these have not changed, so avoid the cost of a write barrier if not needed. if (previousSyncCtx1 != currentThread1._synchronizationContext) { // Restore changed SynchronizationContext back to previous currentThread1._synchronizationContext = previousSyncCtx1; } ExecutionContext? previousExecutionCtx1 = previousExecutionCtx; ExecutionContext? currentExecutionCtx1 = currentThread1._executionContext; if (previousExecutionCtx1 != currentExecutionCtx1) { ExecutionContext.RestoreChangedContextToThread(currentThread1, previousExecutionCtx1, currentExecutionCtx1); } } } TaskAwaiter.UnsafeOnCompletedInternal源码: // 源码地址 //https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs#L220 internal static void UnsafeOnCompletedInternal(Task task, IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext) { task.UnsafeSetContinuationForAwait(stateMachineBox, continueOnCapturedContext); } Task.UnsafeSetContinuationForAwait源码: // 源码地址 //https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs#L2513 internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext) { // continueOnCapturedContext == true,走这个分支 if (continueOnCapturedContext) { SynchronizationContext? syncCtx = SynchronizationContext.Current; if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext)) { var tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, stateMachineBox.MoveNextAction, flowExecutionContext: false); // 添加到m_continuationObject,如果添加失败则代表任务已经完成,tc直接执行 if (!AddTaskContinuation(tc, addBeforeOthers: false)) { tc.Run(this, canInlineContinuationTask: false); } return; } else { TaskScheduler? scheduler = TaskScheduler.InternalCurrent; if (scheduler != null && scheduler != TaskScheduler.Default) { var tc = new TaskSchedulerAwaitTaskContinuation(scheduler, stateMachineBox.MoveNextAction, flowExecutionContext: false); if (!AddTaskContinuation(tc, addBeforeOthers: false)) { tc.Run(this, canInlineContinuationTask: false); } return; } } } // Otherwise, add the state machine box directly as the continuation. // If we're unable to because the task has already completed, queue it. if (!AddTaskContinuation(stateMachineBox, addBeforeOthers: false)) { ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, preferLocal: true); } } SynchronizationContextAwaitTaskContinuation源码: // 源码地址 //https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs#L364 /// <summary>Task continuation for awaiting with a current synchronization context.</summary> internal sealed class SynchronizationContextAwaitTaskContinuation : AwaitTaskContinuation { /// <summary>SendOrPostCallback delegate to invoke the action.</summary> private static readonly SendOrPostCallback s_postCallback = static state => { Debug.Assert(state is Action); ((Action)state)(); }; /// <summary>Cached delegate for PostAction</summary> private static ContextCallback? s_postActionCallback; /// <summary>The context with which to run the action.</summary> private readonly SynchronizationContext m_syncContext; internal SynchronizationContextAwaitTaskContinuation( SynchronizationContext context, Action action, bool flowExecutionContext) : base(action, flowExecutionContext) { Debug.Assert(context != null); m_syncContext = context; } internal sealed override void Run(Task task, bool canInlineContinuationTask) { // If we're allowed to inline, run the action on this thread. if (canInlineContinuationTask && m_syncContext == SynchronizationContext.Current) { RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask); } // Otherwise, Post the action back to the SynchronizationContext. else { TplEventSource log = TplEventSource.Log; if (log.IsEnabled()) { m_continuationId = Task.NewId(); log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId); } // 执行PostAction RunCallback(GetPostActionCallback(), this, ref Task.t_currentTask); } // Any exceptions will be handled by RunCallback. } private static void PostAction(object? state) { Debug.Assert(state is SynchronizationContextAwaitTaskContinuation); var c = (SynchronizationContextAwaitTaskContinuation)state; TplEventSource log = TplEventSource.Log; if (log.IsEnabled() && log.TasksSetActivityIds && c.m_continuationId != 0) { // 调用Control.BeginInvoke c.m_syncContext.Post(s_postCallback, GetActionLogDelegate(c.m_continuationId, c.m_action)); } else { c.m_syncContext.Post(s_postCallback, c.m_action); // s_postCallback is manually cached, as the compiler won't in a SecurityCritical method } } private static Action GetActionLogDelegate(int continuationId, Action action) { return () => { Guid activityId = TplEventSource.CreateGuidForTaskID(continuationId); System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out Guid savedActivityId); try { action(); } finally { System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId); } }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ContextCallback GetPostActionCallback() => s_postActionCallback ??= PostAction; } Task.Delay实现过程

Task.Delay有多种实现,我精简后画了大致实现流程,感兴趣的同学可以阅读一下源码,部分在coreclr实现。

QueueUseAPC: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-queueuserapc

SleepEx: https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleepex

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzsggs.html