首頁

目前文章總數:210 篇

  

最後更新:2025年 12月 13日

0105. SignalR 的 A task was canceled 錯誤,合適的處理方法

日期:2025年 12月 20日

標籤: Asp.NET Core Asp.NET Core Web MVC C# SignalR dotnet runtime

摘要:C# 學習筆記


範例所需:Visual Studio 2022 以上版本
解決問題:實務上引入 SignalR 後經常出現 A task was canceled 的 Error Log 錯誤,要如何正確處理
範例檔案:本篇範例代碼
基本介紹:本篇分為四大部分。
第一部分:問題描述
第二部分:如何重現此問題
第三部分:解決方案
第四部分:結論






第一部分:問題描述

Step 1:問題描述 - 生產環境出現重複Log

在生產環境中的 GrayLog 中經常可以觀察到 Error Level 的信,並且都是 A task was canceled.
而且都是在工作時間出現 08:00 - 24:00 間,半夜都不會出現,但有關鍵字 /ClientHub 因此定位出 SignalR 不斷報此錯誤
問題是:為何會造成 & 最優解決方案為何 ??


Step 2:造成原因說明

在代碼中,我們追蹤到此錯誤是 .NET 原生的錯誤 ,在補捉 SignalR 的例外錯誤時,直接將 ex.Message 紀錄的關係

public class CustomerFilter : IHubFilter
{
    public async ValueTask<object> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next)
    {
        var result = new object();            
        try
        {
            result = await next(invocationContext);
        }
        catch (Exception ex)
        {
            // 問題在此捕捉到 A Task was Canceled.
            Console.WriteLine(ex.Message);                
        }
        return result;
    }
}




會拋出此錯誤通常為以下狀況:

1. 客戶端在執行中關閉連線
2. 網路連線不穩,突然斷開
3. 負載平衡 (SLB) 切換連線


但上述 3 種都有一個共通點, 找不到客戶端訪問 ,導致伺服器端無法正常響應,如下圖解:




第二部分:如何重現此問題

Step 1:範例代碼架構

網路上很容易可以找出相關資源,但大多是說明的文字,我們可以進行重現驗證,保證是此問題造成
打開本篇範例代碼後,架構基本分成以下:

1. Redis 業務邏輯 獨立 Redis 的業務邏輯,並且在初始化時注入為 SingalTon 使其全域共用
2. 初始化配置 依賴注入 Reids、SignalR 等配置,捕捉例外的擴充在此配置
3. 配置 每個 Server 為了辨識,增加自己的代號、使用的 Port 號、Redis Server 位置
4. SignalR Hub & Filter Web伺服器實現 SignalR,為了快速重現問題, 關鍵代碼在這裡
5. 前端頁面 提供聊天室註冊 SignalR 並且可發送訊息,與接收訊息功能




Step 2:範例代碼說明

我們添加以下 1. 2. 段代碼,代碼重現的準備就完成了,接著需要實際操作

/// <summary>
/// 接收前端傳送訊息
/// </summary>                
public async Task SendMessage(string user, string message)
{
    // 1. 重現步驟 - 使用 HubContext 內建的 CT
    var ct = Context.ConnectionAborted;
    await Task.Delay(10000, ct); // 2. 重現步驟 - 帶入 ct,客戶端中斷就會丟 TaskCanceledException
    var dataEntity = new SignalRMessagesEntity()
    {
        Message = message,
        SiteValues = _siteNumber,
        UserName = user,
        CreateTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
    };
    var jsonData = JsonConvert.SerializeObject(dataEntity);
    await _redisService.GetDb(0).SortedSetAddAsync(_RedisKey, jsonData, dataEntity.CreateTime);            
    await Clients.All.SendAsync("ReceiveMessage", jsonData.ToString(), ct);
}



Step 3:DEMO 操作流程 & 重現結果

接著我們要進行以下操作,執行 DEBUG 模式

1. [客戶端] 執行頁面上的按鈕,呼叫 SendMessage 呼叫 SignalR 推送消息
2. [後端] 這時候會收到客戶端消息,我們設定 10 秒鐘延遲
3. [客戶端] 在這 10 秒鐘的延遲中,按下 F5 重新整理頁面
4. [後端] 最終就可以看到 Exception 捕捉此錯誤


如下圖,為整個重現的操作流程 Console Log 就出現 A task was canceled.




第三部分:解決方案

Step 1:處理方案 - 直接忽略

在實務中,遇到此問題,實務上最佳解法都是直接忽略,在代碼中應增加 TaskCanceledException , OperationCanceledException

public async ValueTask<object> InvokeMethodAsync(
  HubInvocationContext invocationContext, 
  Func<HubInvocationContext, ValueTask<object>> next)
{
    var result = new object();            
    try
    {
        result = await next(invocationContext);
    }
    catch (TaskCanceledException)
    {
        // 1. 正常中斷,不記 Error,不回前端,這行甚至可以註解
        Log.Debug($"[{ctx.HubMethod.Name}] client disconnected");        
    }
    catch (OperationCanceledException)
    {
        // 2. 正常中斷,這行甚至可以註解
        Log.Debug($"[{ctx.HubMethod.Name}] client connection aborted");
    }
    catch (Exception ex)
    {
        // 3. 真正的錯誤才記錄
        Log.Error(ex, $"[{ctx.HubMethod.Name}] error occurred");
    }
    return result;
}



Step 2:為何可以直接忽略 - 參考微軟文獻

Microsoft 微軟的 Working with Groups in SignalR說明
其中關鍵字段:

In general, you should not include await when calling the Groups.Remove method because the connection id that you are trying to remove might no longer be available. 
In that case, TaskCanceledException is thrown after the request times out. 
If your application must ensure that the user has been removed from the group before sending a message to the group, 
you can add await before Groups.Remove, and then catch the TaskCanceledException exception that might be thrown.


微軟已知道會有逾時的狀況,一般情況下,建議自行將 TaskCanceledException 捕捉到時排除


Step 3:比較其他處理方式

以下幾種處理是比較常見的作法,但也不能避免紀錄無謂的 A task was canceled.,只能減少 Log 紀錄量
因此最佳解 : Microsoft 提供的作法 - 直接忽略 (亦是實務上大家公認的作法)

項目 說明
[前端代碼] 實現 AutomaticReconnect 只能重建連線,無法避免 Task 被取消
[前端代碼] 依靠前端 retry 沒用,中斷仍會發生
[SLB處理] 延長 timeout 只能減少「過早 timeout」,不能避免切換網路、關閉頁面
[後端代碼] SignalR 延長 timeout 同上





第四部分:結論

Step 1:總結 & 效益

正確處理 TaskCanceledException 與 OperationCanceledException,不是為了避免錯誤,而是為了讓系統能分辨「正常中斷」與「異常失敗」。
這是所有 SignalR 服務在走向穩定、高併發、可維護架構時的必要步驟。
效益總表列表:

效益項目 說明
1. Log 更乾淨、可閱讀性提升 避免被大量「非錯誤」的 TaskCanceledException 洗版,僅保留真正需要處理的錯誤。
2. 提高可維護性,避免誤判為系統異常 開發團隊不會因為正常中斷誤以為是錯誤,減少調查成本。
3. 避免前端收到無意義錯誤訊息 中斷不再被當成錯誤回傳給前端,提升使用者體驗。
4. 真正錯誤更容易被清楚捕捉 當取消例外被過濾後,其他業務錯誤會更明確,更容易 Debug。
5. 提升系統穩定度與高可用性(HA) 在高併發應用中維持正常行為與錯誤的清晰界線,使系統更可預測。
6. 節省後端資源,避免執行無效任務 當使用者中斷連線時,後端可立即停止作業,降低 CPU、Thread、DB 的浪費。