C# 即使在不同的上下文上运行,异步也会等待死锁

C# 即使在不同的上下文上运行,异步也会等待死锁,c#,async-await,task,deadlock,contextswitchdeadlock,C#,Async Await,Task,Deadlock,Contextswitchdeadlock,正如quick pre text一样,我知道是什么导致异步等待死锁问题,但仍然存在问题。希望我忽略了一些简单的事情 我有一个有趣的问题,我正在扩展实体框架IdentityDBContext的保存功能。我正在扩展它并覆盖这些方法 int SaveChanges(); Task<int> SaveChangesAsync(); Task<int> SaveChangesAsync(CancellationToken) 现在在ExecutePreSaveTasks()函数中有

正如quick pre text一样,我知道是什么导致异步等待死锁问题,但仍然存在问题。希望我忽略了一些简单的事情

我有一个有趣的问题,我正在扩展实体框架IdentityDBContext的保存功能。我正在扩展它并覆盖这些方法

int SaveChanges();
Task<int> SaveChangesAsync();
Task<int> SaveChangesAsync(CancellationToken)
现在在ExecutePreSaveTasks()函数中有以下代码(为了清楚起见,省略了无用的代码)

private async Task ExecutePreSaveTask(){
    ValidateFields(); //Synchronous method returns void
    await CheckForCallbacks();
}

private async Task CheckForCallbacks(){
    //loop here that gets changed entities
    var eInsert = changedEntity.Entity as IEntityInsertModifier;
    var eUpdate = changedEntity.Entity as IEntityUpdateModifier;
    var eDelete = changedEntity.Entity as IEntityDeleteModifier;

    if (eInsert != null && changedEntity.State == EntityState.Added) await eInsert.OnBeforeInsert(this);
    if (eUpdate != null && changedEntity.State == EntityState.Modified) await eUpdate.OnBeforeUpdate(this);
    if (eDelete != null && changedEntity.State == EntityState.Deleted) await eDelete.OnBeforeDelete(this);
}
现在,这一部分是关键。在上面的一个“onbeforeensert”调用中,有一个回调DataContext调用“SaveChangesAsync”,等待调用

public async Task OnBeforeInsert(RcmDataContext context)
{
    await context.SaveChangesAsync();
    //some more code
}
最后在SaveChangesAsync中

public override async Task<int> SaveChangesAsync()
{
    //some code that doesn't even run when this is called

    return await base.SaveChangesAsync();
}
这种等待永远不会回来!现在我的理解是当我打电话时

Task.Run(操作)

我正在提供一个新的SynchronizationContext,回调可以在其上运行。这将确保我不会出现死锁情况。事实上,我在执行Task.run之前已经调试并验证了这一点。我在DispatchersSynchronizationContext上,当我在SaveChangesSync中等待真正的异步调用时,我在线程池上下文上(当前上下文为空)。但是死锁仍然发生

是内部savechangessync调用执行了一些特殊的逻辑导致了这种情况,还是我的理解有缺陷?感谢那些花时间阅读并尝试提供帮助的人


p、 我在所有任务上都尝试过ConfigureWait(false),只是想看看它是否有帮助,但没有。我的一位同事找到了问题的解决方案。这是实体框架和Caliburn.Micro BindableCollection的问题

每当集合发生更改时,Caliburn.Micro集合都会在UI线程上触发属性更改事件。当通过数据上下文保存数据时,实体框架正在变异集合,导致其调用UI线程上的事件。由于UI忙于等待任务完成,因此无法调用该方法并…死锁


我想这个故事的寓意是理解您的第三方库。在切换到ObservableCollection后,问题就消失了。

因为ExecutePresavetTask是通过Task执行的。Run,它应该在后台线程上运行,该链中的任何
Wait
都应该等待返回到该线程,而不是UI线程,因此at不应导致与
等待
(著名的临终遗言)的死锁
ConfigureAwait
在这里可能有助于避免切换线程;但这并不是问题的解决方案。现在,在调用回调之前捕获上下文并在调用回调时使用该上下文是很常见的。如果该上下文是UI上下文,则可以解释死锁,因为UI在
Wait
上被阻止。…真正的解决方案是永远不要在UI线程上等待。UI线程是一个单线程单元,您有一个隐含的和预期的契约,不在该线程上等待(比这更复杂;但我发现更容易想到“永远不要在UI线程上等待”)。根据您的具体情况,在等待之前取消
Wait
并禁用按钮,然后在等待之后重新启用(在
SaveChanges
或单击处理程序中,如果调用
SaveChanges
)是推荐的方法。我知道这样的问题总有解决的办法,但我的主要目标是理解为什么它不遵守我认为async await应该遵循的规则。我正在寻找这个问题的深层原因,以便更好地理解实现。我很难理解为什么。等等()在与目标异步调用不同的上下文上调用。为什么会死锁。我在其他领域成功地使用了这种方法,但没有死锁,因此我认为这可能与DbContext如何执行异步调用有关。嗯,
await
很复杂,并且在特定场景w.r.t.UI下工作。没有一种方法能够真正保证工作k如果您阻止UI。任何未直接绑定到UI的内容都应使用
ConfigureAwait(false)
,因为该代码无法了解上下文,在某些情况下,如果没有上下文,将导致问题。
public override async Task<int> SaveChangesAsync()
{
    //some code that doesn't even run when this is called

    return await base.SaveChangesAsync();
}
ButtonClick()
SaveChanges()
Task.Run(() ExecutePreSaveTasks()).Wait()
-->ValidateFields()
-->await CheckForCallbacks()
---->await object.OnBeforeInsert(this)
------>await SaveChangesAsync()
-------->await base.SaveChangesAsync()