0

就像快速的前文一样,我知道是什么导致了异步等待死锁问题,但仍然遇到问题。希望我只是忽略了一些简单的事情。

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

int SaveChanges();
Task<int> SaveChangesAsync();
Task<int> SaveChangesAsync(CancellationToken)

问题是这些调用中的任何一个都可能调用返回可等待任务的对象上的接口方法。这又回到了同步运行异步方法的整个过程中。我已经采取了预防措施来避免死锁,但让我们看看一些代码,这样你就可以看到调用链。

下面是从 UI 按钮单击事件调用的。Task.Run() 用于避免死锁问题。此时,我们处于 UI 上下文中,这就是它将使用 .Wait() 阻止的内容

public override int SaveChanges()
        {
            if (!preSaveExecuting)
            {
                preSaveExecuting = true;
                Task.Run(() => ExecutePreSaveTasks()).Wait();
                preSaveExecuting = false;
            }

            return base.SaveChanges();
        }

现在在 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);
}

现在这部分是关键。在上述“OnBeforeInsert”调用之一中,有一个回调到 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();
}

完整的调用堆栈...

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

这等待永远不会回来!现在我的理解是,当我打电话时

任务.运行(动作)

我提供了一个新的 SynchronizationContext 回调可以在其上运行。这将确保我不会遇到死锁情况。实际上,在执行 Task.Run 之前,我已经调试并验证了我在 DispatcherSynchronizationContext 上,当我在 SaveChangesAsync 中等待真正的异步调用时,我在 ThreadPool 上下文中(当前上下文为空)。然而死锁仍然发生?

内部 SaveChangesAsync 调用是否执行了一些导致此问题的特殊逻辑,或者我的理解有缺陷?感谢那些花时间阅读并尝试提供帮助的人。

ps 我还在所有任务上尝试过 ConfigureAwait(false),只是想看看它是否有帮助,但没有。

4

1 回答 1

0

好吧,我的一位同事找到了解决问题的方法。原来这是 Entity Framework 和 Caliburn.Micro BindableCollection 的问题。

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

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

于 2015-03-27T22:54:56.223 回答