2015-03-13 50 views
-1

就像快速的前文本我知道是什么原因异步等待死锁问题,但我仍然有问题。希望我只是忽略了一些简单的东西。异步等待死锁即使在不同的上下文中运行

我有一个有趣的问题,我扩展了Entity Frameworks 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() 

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

Task.Run(动作)

,我给在其回调可以运行一个新的SynchronizationContext。这将确保我不会遇到死锁情况。事实上,我调试和验证之前,我做Task.Run我在DispatcherSynchronizationContext和当我在SaveChangesAsync,我在一个ThreadPool上下文(当前上下文为空)的真正的异步调用。然而,僵局仍然发生?

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

p.s.我也尝试了所有任务的ConfigureAwait(false),以查看它是否有帮助,但没有。

+0

由于ExecutePreSaveTask正在经由Task.Run执行,应当在后台线程并在该链中的任何'await's运行应等待回该线程而不是UI线程,使得不应引起与死锁'等待'。 (著名遗言); 'ConfigureAwait'在这里很有用,可以避免切换线程;但不是解决您的问题。现在,通常在调用回调之前捕获上下文,并在调用回调时使用该上下文。如果该上下文是UI上下文,那么这将解释死锁,因为用户界面在'Wait'上被阻塞... – 2015-03-13 16:06:01

+3

* real *解决方案是*永远不要等待UI线程*。 UI线程是一个单线程单元,你有一个隐含的和期望的合约,不要在那个线程中等待(它比这更复杂;但是我觉得只是认为“永远不要在UI线程上等待”)。 – 2015-03-13 16:07:07

+0

根据你的情况,建议在Wait(WaitChanges)或点击处理程序(如果调用SaveChanges)后等待并等待后重新启用'Wait'并禁用该按钮。 – 2015-03-13 16:08:28

回答

0

那么我的一位同事找到了解决问题的办法。原来这是实体框架和Caliburn.Micro BindableCollection的问题。

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

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