首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET MVC 4 Async Action + jQuery 實作 Comet 長輪詢訊息通知

     編輯:ASP.NET 基礎

架構概覽

Comet (長輪詢):客戶端發起請求 → 伺服器暫存請求直到有事件或逾時 → 回傳事件 → 客戶端處理後立即發起下一輪。
ASP.NET MVC 4 (.NET 4.5) 引入 async/await,配合 Task<ActionResult> 可輕鬆實作非同步長輪詢,不阻塞 IIS 執行緒池。

核心領域模型


public enum EntityEventType { Create = 0, Update = 1, Deleted = 2 }

public interface IEntityEvent
{
    TEntity[] Entities { get; }
    EntityEventType Type { get; }
}

public class EntityEvent : IEntityEvent
{
    public TEntity[] Entities { get; set; }
    public EntityEventType Type { get; set; }
}

訂閱器 (單例,生產環境建議用 Redis Pub/Sub 或 SignalR)


public sealed class EntityEventSubscriber
{
    private static readonly Lazy _instance 
        = new Lazy(() => new EntityEventSubscriber());
    public static EntityEventSubscriber Instance => _instance.Value;

    // 每個實體類型一個佇列
    private readonly ConcurrentDictionary>>> _waiters
        = new ConcurrentDictionary>>>();

    // 發布事件 (由業務層呼叫,如 Repository.SaveChanges 後)
    public void Publish(EntityEventType type, params TEntity[] entities)
    {
        var eventObj = new EntityEvent { Type = type, Entities = entities };
        var queue = _waiters.GetOrAdd(typeof(TEntity), _ => new Queue>>());
        lock (queue)
        {
            while (queue.Count > 0)
            {
                var tcs = queue.Dequeue();
                tcs.TrySetResult(eventObj);
            }
        }
    }

    // 非同步等待事件
    public async Task> WaitForEventAsync(int timeoutMs)
    {
        var tcs = new TaskCompletionSource>();
        var queue = _waiters.GetOrAdd(typeof(TEntity), _ => new Queue>>());
        lock (queue) queue.Enqueue(tcs);

        var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(timeoutMs));
        if (completedTask == tcs.Task)
            return (IEntityEvent)tcs.Task.Result;
        return null; // 逾時
    }
}

Controller 非同步 Action


[SessionState(SessionStateBehavior.ReadOnly)] // 關鍵:避免 Session 鎖死其他請求
public class EventController : Controller
{
    // GET /event/ImageSet?millisecondsTimeout=10000
    public async Task ImageSet(int millisecondsTimeout = 10000)
    {
        var evt = await EntityEventSubscriber.Instance.WaitForEventAsync(millisecondsTimeout);
        return Json(new
        {
            HasEvent = evt != null,
            EntityEvent = evt
        }, JsonRequestBehavior.AllowGet);
    }
}

// 實體資料類別
public class ImageSetData
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime UpdatedAt { get; set; }
}

前端 jQuery 長輪詢迴圈



關鍵技術細節解析

1. [SessionState(SessionStateBehavior.ReadOnly)]

預設 MVC Controller 對 Session 有寫入鎖定。長輪詢請求持有 Session 鎖 30 秒會導致同使用者其他頁面請求排隊阻塞。標記 ReadOnly 釋放鎖定,允許並行請求。

2. async/await 不阻塞執行緒

await Task.Delay 或 await tcs.Task 釋放執行緒回執行緒池,單一 IIS 能支援數萬並發長連線。

3. 記憶體洩漏防護

  • TaskCompletionSource 必須在逾時或完成時移出佇列 (上方 Dequeue 已處理)。
  • 若客戶端斷線 (瀏覽器關閉),伺服器端 Task.Delay 逾時自然釋放。

4. 多伺服器擴展 (Web Farm)

單機 ConcurrentDictionary 無法跨節點共享事件。生產環境必須替換為:

  • Redis Pub/Sub:發布到 Channel,各節點訂閱並完成本地 TaskCompletionSource。
  • SignalR (ASP.NET Core):內建 Scaleout (Redis/Azure SignalR Service),自動處理連線管理、重連、分組。

SignalR 現代化替代 (強烈推薦)


// ASP.NET Core + SignalR Hub
public class NotificationHub : Hub
{
    public async Task Subscribe(string entityType) 
        => await Groups.AddToGroupAsync(Context.ConnectionId, entityType);
}

public class ImageSetService
{
    private readonly IHubContext _hub;
    public async Task NotifyCreated(ImageSetData data)
        => await _hub.Clients.Group("ImageSet").SendAsync("Created", data);
}

// 前端
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/notificationHub").build();
connection.on("Created", (data) => addToList(data));
await connection.start();
await connection.invoke("Subscribe", "ImageSet");

優點:WebSocket/Server-Sent Events/長輪詢自動協商、自動重連、分組廣播、Scaleout 開箱即用。

效能與擴展性對比

方案單機並發連線跨節點實作複雜度瀏覽器相容
MVC Async 長輪詢~20,000 (受限記憶體)需自建 Redis Pub/Sub中全瀏覽器
SignalR (Core)~50,000+ (WebSocket)內建 Scaleout低IE10+ (需 Polyfill)
WebSocket 手工~100,000自建高IE10+
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved