爲何不要使用 async void?

問題

在使用 Abp 框架的後臺做業時,當後臺做業拋出異常,會致使整個程序崩潰。在 Abp 框架的底層執行後臺做業的時候,有 try/catch 語句塊用來捕獲後臺任務執行時的異常,可是在這裏沒有生效。html

原始代碼以下:框架

public class TestAppService : ITestAppService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

    public TestAppService(IBackgroundJobManager backgroundJobManager)
    {
        _backgroundJobManager = backgroundJobManager;
    }

    public Task GetInvalidOperationException()
    {
        throw new InvalidOperationException("模擬無效操做異常。");
    }

    public async Task<string> EnqueueJob()
    {
        await _backgroundJobManager.EnqueueAsync<BG, string>("測試文本。");

        return "執行完成。";
    }
}

public class BG : BackgroundJob<string>, ITransientDependency
{
    private readonly TestAppService _testAppService;

    public BG(TestAppService testAppService)
    {
        _testAppService = testAppService;
    }

    public override async void Execute(string args)
    {
        await _testAppService.GetInvalidOperationException();
    }
}

調用接口時的效果:異步

緣由

出現這種狀況是由於任何異步方法返回 void 時,拋出的異常都會在 async void 方法啓動時,處於激活狀態的同步上下文 (SynchronizationContext) 觸發,咱們的全部 Task 都是放在線程池執行的。async

因此在上述樣例當中,此時 AsyncVoidMethodBuilder.Create() 使用的同步上下文爲 null ,這個時候 ThreadPool 就不會捕獲異常給原有線程處理,而是直接拋出。ide

線程池在底層使用 AsyncVoidMethodBuilder.Craete() 所拿到的同步上下文,所捕獲異常的代碼以下:post

internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext)
{
    var edi = ExceptionDispatchInfo.Capture(exception);

    // 同步上下文是空的,則不會作處理。
    if (targetContext != null)
    {
        try
        {
            targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi);
            return;
        }
        catch (Exception postException)
        {
            edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException));
        }
    }
}

雖然你能夠經過掛載 AppDoamin.Current.UnhandledException 來監聽異常,不過你是沒辦法從異常狀態恢復的。測試

參考文章:ui

Stephen Cleary: https://msdn.microsoft.com/en-us/magazine/jj991977.aspxspa

Jerome Laban's:https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html線程

布魯克石:http://www.javashuo.com/article/p-gdtdbjat-dv.html

解決

能夠使用 AsyncBackgroundJob<TArgs> 替換掉以前的 BackgroundJob<TArgs> ,只須要實現它的 Task ExecuteAsync(TArgs args) 方法便可。

public class BGAsync : AsyncBackgroundJob<string>,ITransientDependency
{
    private readonly TestAppService _testAppService;

    public BGAsync(TestAppService testAppService)
    {
        _testAppService = testAppService;
    }

    protected override async Task ExecuteAsync(string args)
    {
        await _testAppService.GetInvalidOperationException();
    }
}
相關文章
相關標籤/搜索