2015-03-13 49 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後,問題消失了。