如何在 Blazor 服务器应用程序中正确取消?
How to do proper cancellation within a blazor server app?
我有一个方法绑定到 table 行的点击事件。被调用的方法是:
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();
取消之前的任务。 _belege
是一个 List<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;
}
}
}
}
}
}
如您所见,当请求取消时,我 add/return 什么也没做。但是,当我快速连续单击 table 行时,我会在列表中看到最多 3 次相同的项目。如果我在再次调用我的方法之前取消所有旧的 运行 方法,这怎么可能呢?有谁知道我在这里应该做些什么?
How is this even possible if I cancel all old running methods before I call my method again? Does anyone know what I should do different here?
GetBelegeMitPositionAsync
正在检查已取消的 CTS 的取消令牌;但是BestelldispositionsItemClicked
中的await foreach
循环是直接检查CTS成员变量,旧的取消后就变了。
我强烈建议遵循 .NET 的标准取消模式。这种模式意味着取消的代码不应该 return;它应该抛出 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;
}
}
我有一个方法绑定到 table 行的点击事件。被调用的方法是:
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();
取消之前的任务。 _belege
是一个 List<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;
}
}
}
}
}
}
如您所见,当请求取消时,我 add/return 什么也没做。但是,当我快速连续单击 table 行时,我会在列表中看到最多 3 次相同的项目。如果我在再次调用我的方法之前取消所有旧的 运行 方法,这怎么可能呢?有谁知道我在这里应该做些什么?
How is this even possible if I cancel all old running methods before I call my method again? Does anyone know what I should do different here?
GetBelegeMitPositionAsync
正在检查已取消的 CTS 的取消令牌;但是BestelldispositionsItemClicked
中的await foreach
循环是直接检查CTS成员变量,旧的取消后就变了。
我强烈建议遵循 .NET 的标准取消模式。这种模式意味着取消的代码不应该 return;它应该抛出 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;
}
}