The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

ASP.NET 與 ASP․NET Core 偵測用戶端已斷線並自動取消非同步方法執行

我今天在「C# 開發實戰:非同步程式開發技巧」課程中有學員提問,如何偵測用戶端已斷線並自動取消非同步方法執行。事實上 ASP.NET (.NET Framework) 與 ASP.NET Core 的實作方式相當類似,今天這篇文章我就來分享 ASP.NET Core 的實作方式,幫助你有效偵測用戶端已斷線,並自動取消非同步方法執行。

Canceled

在 ASP.NET Core 中,直接就有個 Request.HttpContext.RequestAborted 屬性可用,而這個屬性的型別恰恰就是 System.Threading.CancellationToken 結構,就是這麼佛心來著! 😍

由於幾乎所有的非同步方法,都有個 CancellationToken 參數可以傳入,今後你不需要特別建立一個 CancellationTokenSource 來自行管理取消非同步的判斷,直接把 Request.HttpContext.RequestAborted 拿去用即可。例如:

  • 實作一個寫入 512MB 檔案,並在用戶端斷線的時候,立即停止寫入,並刪除寫入到一半的檔案

    const string filePath = @"G:\big-file.log";
    try
    {
        await System.IO.File.WriteAllTextAsync(filePath,
                string.Join("", Enumerable.Repeat("A", 1024 * 1024 * 500)),
                Request.HttpContext.RequestAborted);
    }
    catch (TaskCanceledException) when (System.IO.File.Exists(filePath))
    {
        System.IO.File.Delete(filePath);
    }
    

    上述 try/cache 的 when 是 C# 6.0 的例外篩選條件語法,當例外發生條件成立(有檔案存在)才會刪除檔案。

  • 透過 EF Core 5.0 寫入 100 萬筆資料,並在用戶端斷線的時候 Rollback 所有寫到一半的資料

    由於 DbContext.SaveChangesAsync 方法本身就預設包含了交易處理,因此當 CancellationToken 進入取消狀態的時候,寫入到一半的交易就會自動 Rollback,不會殘留髒資料。

    for (int i = 0; i < 1_000_000; i++)
    {
        var d = new Department();
        d.InjectFrom(Department);
    
        _context.Department.Add(d);
    }
    
    await db.SaveChangesAsync(Request.HttpContext.RequestAborted);
    
    if (Request.HttpContext.RequestAborted.IsCancellationRequested)
    {
        // 如果你想在最後檢查用戶端是否斷線,如果斷線可以在這裡進行一些髒資料清理動作!
    }
    

    db.SaveChangesAsync() 這個 Task 被取消的時候會拋出 System.Threading.Tasks.TaskCanceledException 例外,不過由於使用者已經斷線,因此他也不可能看到任何錯誤訊息,但 ASP.NET Core 的 ILogger 會詳實的記錄到這個例外狀況,你可以視情況決定要不要隱藏這些記錄。

有了上述兩個例子,我想應該足以讓你應付所有類似的狀況! 😎

對了,如果你想在 ASP.NET MVC 或 ASP.NET Web API 實現類似機制,從 .NET Framework 4.5 開始,就有個 System.Web.HttpContext.Current.Response.ClientDisconnectedToken 可用,這個屬性的型別也是個 System.Threading.CancellationToken 結構,因此開發與使用的方式完全一樣!😍

如果是更早之前 .NET Framework 版本,也有個 HttpResponse.IsClientConnected 屬性可用。

相關連結

留言評論