【问题标题】:How to do proper cancellation within a blazor server app?如何在 blazor 服务器应用程序中正确取消?
【发布时间】:2021-09-10 20:55:00
【问题描述】:

我有一个绑定到表格行中的点击事件的方法。被调用的方法是:

private async Task BestelldispositionsItemClicked(Bestelldisposition pos)
{
    _tokenSourceClicked.Cancel();
    _tokenSourceClicked = new CancellationTokenSource();
    _belege.Clear();
    using FbController2 fbController = new FbController2();
    _isLoading = true;
    _selectedBestelldisposition = pos;

    await foreach (var beleg in pos.GetBelegeMitPositionAsync(fbController, _tokenSourceClicked.Token))
    {
        if (_tokenSourceClicked.IsCancellationRequested)
        {
            break;
        }

        _belege.Add(beleg);
        StateHasChanged();
    }


    _isLoading = false;
}

如您所见,我正在使用_tokenSourceClicked.Cancel(); 取消上一个任务。 _belegeList<Beleg>,之后会被清除。令牌被传递给另一个函数GetBelegeMitPositionAsync,其定义为:

public async IAsyncEnumerable<Auftrag> GetBelegeMitPositionAsync(FbController2 fbController, [EnumeratorCancellation] CancellationToken cancellationToken)
{
    if (!cancellationToken.IsCancellationRequested)
    {
        fbController.AddParameter("@BPOS_A_ARTIKELNR", Artikelnummer);
        var data = await fbController.SelectDataAsync(@"SELECT BELE_N_NR FROM BELEGPOS BP 
INNER JOIN BELEGE B ON (B.BELE_N_NR = BP.BPOS_N_NR AND B.BELE_A_TYP = 'AU')
WHERE BPOS_A_TYP = 'AU' AND BPOS_A_ARTIKELNR = @BPOS_A_ARTIKELNR
AND BELE_L_ERLEDIGT = 'N'");


        foreach (DataRow row in data.Rows)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                break;
            }
            int belegnummer = row.Field<int>("BELE_N_NR");

            if (belegnummer > 0)
            {
                var auftrag = await Auftrag.GetAuftragAsync(belegnummer, fbController);

                if (auftrag is not null)
                {
                    if (!cancellationToken.IsCancellationRequested)
                    {
                        yield return auftrag;
                    }
                }
            }
        }
    }
}

如您所见,当请求取消时,我不会添加/返回任何内容。但是,当我快速连续单击表格行时,我会在列表中看到最多 3 次相同的项目。如果我在再次调用我的方法之前取消所有旧的运行方法,这怎么可能?有谁知道我在这里应该做些什么不同的事情?

【问题讨论】:

  • 只是在这里猜测,但是当通过取消 StateHasChanged() 中断 for 循环时是否会出现 UI 问题;可能不会被调用?
  • @jeb 没有。这与它无关。框架在方法结束时自动调用 StateHasChanged。

标签: c# blazor-server-side cancellation cancellationtokensource


【解决方案1】:

如果我在再次调用我的方法之前取消所有旧的运行方法,这怎么可能?有谁知道我在这里应该做些什么不同的事情?

GetBelegeMitPositionAsync 正在检查已取消 CTS 的取消令牌;但是BestelldispositionsItemClicked中的await foreach循环是直接检查CTS成员变量,在旧的取消后改变。

我强烈建议遵循 .NET 的标准取消模式。这种模式意味着取消的代码不应该返回;它应该抛出OperationCanceledException。这很容易通过将所有IsCancellationRequested 检查替换为对ThrowIfCancellationRequested 的调用来完成。此外,所有可取消的代码都应使用(副本)CancellationToken,而不是直接检查CancellationTokenSource

例如:

private async Task BestelldispositionsItemClicked(Bestelldisposition pos)
{
  _tokenSourceClicked.Cancel();
  _tokenSourceClicked = new CancellationTokenSource();
  var token = _tokenSourceClicked.Token;
  _belege.Clear();
  using FbController2 fbController = new FbController2();
  _isLoading = true;
  _selectedBestelldisposition = pos;

  try
  {
    await foreach (var beleg in pos.GetBelegeMitPositionAsync(fbController, token))
    {
      _belege.Add(beleg);
      StateHasChanged();
    }
  }
  catch (OperationCanceledException) { }
  finally
  {
    _isLoading = false;
  }
}

【讨论】:

  • 非常感谢这个例子。关于令牌副本的最后一个问题。我现在如何从 IAsyncDisposable 取消此操作?我在类级别声明了我的 CancellationTokenSource 以在 DisposeAsync 中访问它,但我不需要调用 Cancel();来自复制的令牌?
  • @MarvinKlein:如果这个类型实现了IAsyncDisposable,你仍然可以从DisposeAsync调用Cancel。您只是不希望 消费 代码访问CancellationTokenSource
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-18
  • 2020-07-04
  • 2020-09-05
  • 2019-12-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多