簡體   English   中英

如何使用信號器以正確的方式處理更新?

[英]How to handle updates in the right way using signalr?

我有一個客戶端應用程序Angular和一個signalR集線器,還有一個將時間戳作為參數的服務。

當我按下客戶端中的開始按鈕時,我想調用集線器中的一個方法,當調用該方法時,我想繼續列出所有更改(創建一個計時器),直到客戶端按下停止按鈕,然后我將停止計時器。

所以我想問一下哪個更好:

1- 使用時間戳從客戶端調用被調用的方法,然后創建一個setInterval來調用其中的方法,當按下停止按鈕時,我可以停止它。

優點:啟動和停止計時器很容易。

缺點:我每 1 秒調用一次該方法,然后檢查客戶端是否有更新 UI 的響應。

2- 調用一次該方法,然后為服務器上的每個客戶端創建一個計時器,當客戶端按下停止按鈕時,我可以調用另一種方法來停止該客戶端的計時器。

優點:我正在檢查集線器中的時間戳,只有當來自服務的時間戳 > 本地時間戳時,我才會將數據發送到客戶端

缺點:我實際上不知道如何為每個客戶端創建一個計時器,所以如果這是正確的方法,請幫助我

您正在使用SignalR進行實時數據通信。 每秒調用一個方法只是在SignalR臉上開玩笑......所以這不是解決方案。

最好的解決方案是使用組功能。

例子:

  1. 您的開始按鈕會將用戶添加到組中。
  2. 當您的用戶在組中時,它將收到您需要的所有數據。 await this.Clients.Group("someGroup").BroadcastMessage(message);
  3. 您的停止按鈕將從組中刪除該用戶,使其不再接收數據。

集線器上的一些代碼示例:

public async Task Start()
{
    // Add user to the data group

    await this.Groups.AddToGroupAsync(this.Context.ConnectionId, "dataGroup");
}

public async Task Stop()
{
    // Add user to the data group

    await this.Groups.RemoveFromGroupAsync(this.Context.ConnectionId, "dataGroup");
}

向按下啟動鍵的用戶發送數據並接收實時數據的 Worker 示例。

private readonly IHubContext<SignalRHub, ISignalRHub> hub;

private readonly IServiceProvider serviceProvider;

public Worker(IServiceProvider serviceProvider, IHubContext<SignalRHub, ISignalRHub> hub)
{
    this.serviceProvider = serviceProvider;
    this.hub = hub;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        await this.hub.Clients.Group("dataGroup").BroadcastMessage(DataManager.GetData());

        this.Logger.LogDebug("Sent data to all users at {0}", DateTime.UtcNow);

        await Task.Delay(1000, stoppingToken);
    }
}

PS:如果你有工人,我假設你有一些經理來獲取數據或要發送給用戶的東西。

編輯:如果你不想用戶工作者,你可以總是像這樣的計時器:

public class TimerManager
{
    private Timer _timer;
    private AutoResetEvent _autoResetEvent;
    private Action _action;
    public DateTime TimerStarted { get; }

    public TimerManager(Action action)
    {
        _action = action;
        _autoResetEvent = new AutoResetEvent(false);
        _timer = new Timer(Execute, _autoResetEvent, 1000, 2000);
        TimerStarted = DateTime.Now;
    }

    public void Execute(object stateInfo)
    {
        _action();
        if((DateTime.Now - TimerStarted).Seconds > 60)
        {
            _timer.Dispose();
        }
    }
}

然后在某處使用它,例如:

 var timerManager = new TimerManager(() => this.hub.Clients.Group("dataGroup").BroadcastMessage(DataManager.GetData()));

選項 #1 不可用,因為 SignalR 的存在是為了消除輪詢的需要。 頻繁的輪詢也不能擴展。 如果每個客戶端每 1 秒輪詢一次服務器,該網站最終將白白付出大量CPU 和帶寬。 商務人士也不喜歡頻繁輪詢,因為所有托管商和雲提供商都對出口收費。

SignalR 流示例使用定時通知作為使用IAsyncEnumerable<T>的流通知的簡單示例。 在最簡單的例子中,計數器每delay毫秒遞增一次:

public class AsyncEnumerableHub : Hub
{
    public async IAsyncEnumerable<int> Counter(
        int count,
        int delay,
        [EnumeratorCancellation]
        CancellationToken cancellationToken)
    {
        for (var i = 0; i < count; i++)
        {
            // Check the cancellation token regularly so that the server will stop
            // producing items if the client disconnects.
            cancellationToken.ThrowIfCancellationRequested();

            yield return i;

            // Use the cancellationToken in other APIs that accept cancellation
            // tokens so the cancellation can flow down to them.
            await Task.Delay(delay, cancellationToken);
        }
    }
}

客戶端可以調用此操作傳遞所需的延遲並開始接收通知。 SignalR 知道這是一個通知流,因為它返回IAsyncEnumerable

下一個更高級的示例使用Channels來允許發布者方法WriteItemsAsync向中心發送通知流。

動作本身更簡單,它只是返回頻道的閱讀器:

public ChannelReader<int> Counter(
    int count,
    int delay,
    CancellationToken cancellationToken)
{
    var channel = Channel.CreateUnbounded<int>();

    // We don't want to await WriteItemsAsync, otherwise we'd end up waiting 
    // for all the items to be written before returning the channel back to
    // the client.
    _ = WriteItemsAsync(channel.Writer, count, delay, cancellationToken);

    return channel.Reader;
}

發布者方法寫入ChannelWriter而不是返回IAsyncEnumerable

private async Task WriteItemsAsync(
    ChannelWriter<int> writer,
    int count,
    int delay,
    CancellationToken cancellationToken)
{
    Exception localException = null;
    try
    {
        for (var i = 0; i < count; i++)
        {
            await writer.WriteAsync(i, cancellationToken);

            // Use the cancellationToken in other APIs that accept cancellation
            // tokens so the cancellation can flow down to them.
            await Task.Delay(delay, cancellationToken);
        }
    }
    catch (Exception ex)
    {
        localException = ex;
    }

    writer.Complete(localException);
}

這個方法可以很容易地在不同的類中。 所需要做的就是將ChannelWriter傳遞給發布者。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM