來源:https://note.guoqianfan.com/2022/04/23/dont-use-async-void/
前言
之前都是在文檔里看到:除了winform的事件可以使用async void
,其他情況下絕對(duì)不能使用async void
以舒,而是要用async Task
趾痘。
對(duì)于這個(gè)規(guī)范,雖然不是很明白內(nèi)里原因蔓钟,但是一直遵守著永票。
直到這天看到了這篇博客:在 ASP.NET Core 中誤用 async void 竟引發(fā)了 502(Bad Gateway),說async void
里出現(xiàn)異常時(shí)會(huì)導(dǎo)致程序崩潰滥沫。研究測(cè)試了一番侣集,終于明白原因。
摘錄重點(diǎn)如下:
根據(jù)使用者提供的另一個(gè)線索「網(wǎng)站的某個(gè)功能壞了」兰绣,我們繼續(xù)往下追查肚吏,從程式碼當(dāng)中我看到了一個(gè)近期新加的方法,它使用了 async void狭魂,沒錯(cuò)罚攀,它使用了 async void党觅,而且很不幸地它會(huì)發(fā)生 Exception,更慘的是這個(gè) Exception 沒有被處理斋泄。
對(duì) C# 非同步程式設(shè)計(jì)有了解的朋友杯瞻,看到這邊應(yīng)該大致上可以知道是發(fā)什麼問題了,async void 是建議應(yīng)該避免使用的宣告方式炫掐,其中一個(gè)原因就是當(dāng) async void 方法發(fā)生 Exception 時(shí)無法從呼叫端捕獲魁莉,即使加了 try...catch... 也沒用,async void 方法就有點(diǎn)像是我們自己起了另一個(gè) Thread 去執(zhí)行程式一樣募胃,執(zhí)行的過程中如果發(fā)生 Exception 沒有去處理旗唁,Exception 就會(huì)一路被往上拋,最終在 AppDomain 層級(jí)被捕獲痹束,然後我們的應(yīng)用程式就掛了检疫。
async-void-方法的異常無法被捕獲
async void
方法拋出的異常無法被捕獲,異常會(huì)被一直往上面拋祷嘶,最終在AppDomain層級(jí)被捕獲屎媳,然后程序就掛了。
示例代碼如下:
[HttpGet]
public async void Get()
{
try
{
ThrowExceptionAsync();
}
catch (Exception ex)
{
//這里不能捕獲到異常论巍,程序崩潰烛谊!
_logger.LogInformation(ex, "ex...");
}
}
async void ThrowExceptionAsync()
{
throw new Exception("async void ex!");
}
注意
前面所說的是 async void
方法拋出的無法預(yù)知到的異常。在async void
方法內(nèi)部嘉汰,我們?nèi)匀荒軌蚴褂?code>try catch丹禀,邏輯是正常邏輯。具體見下面的示例:
[HttpGet]
public async void Get()
{
//在async void方法內(nèi)部鞋怀,我們?nèi)匀荒軌蚴褂胻ry catch双泪,邏輯是正常邏輯。
//此處try catch是有效的接箫。異常被捕獲處理了攒读,async void方法執(zhí)行無異常朵诫,不會(huì)導(dǎo)致程序崩潰辛友。
try
{
await Task.Run(() =>
{
throw new Exception("ex!");
});
}
catch (Exception ex)
{
_logger.LogInformation(ex,"ex...");
}
}
測(cè)試
崩潰
出現(xiàn)異常時(shí)能導(dǎo)致崩潰的代碼有2種,如下:
[HttpGet]
public async void Get()
{
//異常會(huì)導(dǎo)致程序崩潰
throw new Exception("ex!");
}
[HttpGet]
public async void Get()
{
//異常會(huì)導(dǎo)致程序崩潰
await Task.Run(() =>
{
throw new Exception("ex!");
});
}
注意
下面的async void
代碼不會(huì)拋異常剪返。
[HttpGet]
public async void Get()
{
Task.Run(() =>
{
throw new Exception("ex!");
});
}
代碼里的async void
沒問題(不拋異常)废累,其實(shí)也符合邏輯。因?yàn)?code>async void里面沒有異常脱盲,自然就不會(huì)導(dǎo)致程序崩潰邑滨。
異常在Task.Run
里面,因?yàn)?strong>沒有使用await
進(jìn)行等待钱反,那么異常就是被線程池線程捕獲的掖看,它們捕獲到后匣距,不會(huì)再往上面拋了,直接自己內(nèi)部消化掉了哎壳。
因?yàn)?code>async void在執(zhí)行時(shí)沒有異常毅待,自然就不會(huì)導(dǎo)致程序崩潰。
但是由于我們不能保證所有代碼都沒有異常归榕,所以不要使用async void
尸红!
不崩潰
只要不是async void
,就算請(qǐng)求處理程序拋出了異常刹泄,也不會(huì)影響到主線程的外里。最多就是這次請(qǐng)求出錯(cuò),返回500 Internal Server Error
而已特石。
測(cè)試的幾種代碼如下:
[HttpGet]
public async Task Get()
{
//500錯(cuò)誤碼
throw new Exception("ex!");
}
[HttpGet]
public async Task Get()
{
//500錯(cuò)誤碼
await Task.Run(() =>
{
throw new Exception("ex!");
});
}