EF6中慎用async void

其實正確的說,應該所有地方都慎用async void,應當儘可能的只在用作事件Event方法時才採用async void,其它地方應當將async Task作爲返回值。

項目中使用的是EF6,在測試過程中,產生了一個System.NotSupportedException異常,其內容如下

A second operation started on this context before a previous asynchronous operation completed. 
Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context.
Any instance members are not guaranteed to be thread safe.

提示內容已經很明確的告知我們,要通過await來順序執行所有涉及EF的Task,在stackoverflow上也有此對應的問題。當EF中有多個涉及數據庫的異步操作時,要順序對每個請求進行await,下面分別將stackoverflow上錯誤和正確的寫法羅列下

#region 錯誤會產生異常的寫法
var banner = context.Banners.ToListAsync()
var newsGroup = context.NewsGroups.ToListAsync()
await Task.WhenAll(banner, newsGroup);
#endregion

#region 正確的寫法
var banner = await context.Banners.ToListAsync();
var newsGroup = await context.NewsGroups.ToListAsync();
#endregion

檢查了項目中相關部分的代碼,發現所有異步操作都是採用了正確await的寫法,並沒有如stackoverflow上那樣描述類似的問題寫法,那問題究竟在哪呢?反覆檢查代碼,突然發現該部分代碼調用的一個async void方法代碼,該代碼作用是向釘釘發送消息推送,其間需要從數據庫中讀取當前用戶對應釘釘中的id,其大致代碼如下

public async void SendNotice(string uid,string message)
{
//await方式從db中,根據uid讀取其在釘釘中的uid
//如果讀取到,那麼就調用釘釘的sdk進行消息推送
}

之所以這個通知代碼會寫成async void是有歷史原因的,因爲系統中存在大量的同步代碼,以及少量的異步代碼(吐槽下都9102年了,居然還有那麼多人不知道Task),爲了同時兼容同步以及異步代碼,並且這個通知本質上也不會影響業務,所以async void代碼由此產生,而在之前的使用中之所以沒產生System.NotSupportedException異常的原因也很簡單,之前的代碼都是在所有業務數據處理完畢並保存Save之後,纔會調用通知代碼,而異常部分的代碼是業務處理過程中,就進行了通知代碼調用(理論上這是不應該的,畢竟業務數據有可能產生失敗,如果失敗的話,就不應該進行消息推送,只是爲了偷懶,加上這是個定製項目,並不需要嚴格意義上的邏輯正確),而在最後調用Save時,System.NotSupportedException也就閃亮登場。

既然知道了問題產生的原因,解決起來也就簡單了,新增一個返回Task的通知方法,將舊代碼全部複製到新方法裏去,並修改舊方法調用新方法以便兼容系統中現有代碼,

public async void SendNotice(string uid,string message)
{
    await SendNoticeAsync(uid,message);
}
public async Task SendNoticeAsync(string uid,string message)
{
//await方式從db中,根據uid讀取其在釘釘中的uid
//如果讀取到,那麼就調用釘釘的sdk進行消息推送
}

在將異常部分代碼改爲調用await SendNoticeAsync後,異常順利解決。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章