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

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

![Canceled](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/eb7b9f21e439728bed34-174483529-6185cb37-351a-4dee-92cf-0f0b709090cf.jpg)

在 ASP.NET Core 中，直接就有個 [Request.HttpContext.RequestAborted](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httpcontext.requestaborted?view=aspnetcore-5.0&WT.mc_id=DT-MVP-4015686) 屬性可用，而這個屬性的型別恰恰就是 [System.Threading.CancellationToken](https://learn.microsoft.com/zh-tw/dotnet/api/system.threading.cancellationtoken?view=net-5.0&WT.mc_id=DT-MVP-4015686) 結構，就是這麼佛心來著！ 😍

由於幾乎所有的非同步方法，都有個 `CancellationToken` 參數可以傳入，今後你不需要特別建立一個 [CancellationTokenSource](https://learn.microsoft.com/zh-tw/dotnet/api/system.threading.cancellationtokensource?view=net-5.0&WT.mc_id=DT-MVP-4015686) 來自行管理取消非同步的判斷，直接把 `Request.HttpContext.RequestAborted` 拿去用即可。例如：

-   實作一個寫入 512MB 檔案，並在用戶端斷線的時候，立即停止寫入，並刪除寫入到一半的檔案
    
    ```csharp
    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](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/when?WT.mc_id=DT-MVP-4015686) 是 C# 6.0 的例外篩選條件語法，當**例外發生**且**條件成立**(有檔案存在)才會刪除檔案。
    
-   透過 EF Core 5.0 寫入 100 萬筆資料，並在用戶端斷線的時候 Rollback 所有寫到一半的資料
    
    由於 [DbContext.SaveChangesAsync](https://learn.microsoft.com/zh-tw/dotnet/api/system.data.entity.dbcontext.savechangesasync?view=entity-framework-6.2.0&WT.mc_id=DT-MVP-4015686) 方法本身就預設包含了交易處理，因此當 `CancellationToken` 進入**取消**狀態的時候，寫入到一半的交易就會自動 Rollback，不會殘留髒資料。
    
    ```csharp
    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](https://learn.microsoft.com/zh-tw/dotnet/api/system.threading.tasks.taskcanceledexception?view=net-5.0&WT.mc_id=DT-MVP-4015686) 例外，不過由於使用者已經斷線，因此他也不可能看到任何錯誤訊息，但 ASP.NET Core 的 `ILogger` 會詳實的記錄到這個例外狀況，你可以視情況決定要不要隱藏這些記錄。
    

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

對了，如果你想在 ASP.NET MVC 或 ASP.NET Web API 實現類似機制，從 .NET Framework 4.5 開始，就有個 `System.Web.HttpContext.Current.Response.ClientDisconnectedToken` 可用，這個屬性的型別也是個 [System.Threading.CancellationToken](https://learn.microsoft.com/zh-tw/dotnet/api/system.threading.cancellationtoken?view=net-5.0&WT.mc_id=DT-MVP-4015686) 結構，因此開發與使用的方式完全一樣！😍

如果是更早之前 .NET Framework 版本，也有個 [HttpResponse.IsClientConnected](https://learn.microsoft.com/zh-tw/dotnet/api/system.web.httpresponse.isclientconnected) 屬性可用。

### 相關連結

-   [File.WriteAllTextAsync 方法 (System.IO) | Microsoft Docs](https://learn.microsoft.com/zh-tw/dotnet/api/system.io.file.writealltextasync?view=net-5.0#System_IO_File_WriteAllTextAsync_System_String_System_String_System_Text_Encoding_System_Threading_CancellationToken_)
-   [DbContext.SaveChangesAsync 方法 (System.Data.Entity) | Microsoft Docs](https://learn.microsoft.com/zh-tw/dotnet/api/system.data.entity.dbcontext.savechangesasync?view=entity-framework-6.2.0&WT.mc_id=DT-MVP-4015686)
-   [TaskCanceledException 類別 (System.Threading.Tasks) | Microsoft Docs](https://learn.microsoft.com/zh-tw/dotnet/api/system.threading.tasks.taskcanceledexception?view=net-5.0&WT.mc_id=DT-MVP-4015686)
-   [HttpResponse.ClientDisconnectedToken 屬性 (System.Web) | Microsoft Docs](https://learn.microsoft.com/zh-tw/dotnet/api/system.web.httpresponse.clientdisconnectedtoken?view=netframework-4.8&WT.mc_id=DT-MVP-4015686)
-   [How do I cancel non-cancelable async operations?](https://devblogs.microsoft.com/pfxteam/how-do-i-cancel-non-cancelable-async-operations/?WT.mc_id=DT-MVP-4015686)
