I have a method bound to a click event from a table row. The method which gets called is:
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;
}
As you can see I am canceling the previous task with _tokenSourceClicked.Cancel();
. _belege
is a List<Beleg>
which gets cleared afterwards. The token is being passed down to antoher function GetBelegeMitPositionAsync
which is defined as:
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;
}
}
}
}
}
}
As you can see I don't add/return anything when a canellation has being requested. However when I click my table row in quick succession, I'll see the same items up to 3 times within my list. 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?
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
is checking the cancellation token for the CTS that was canceled; but the await foreach
loop in BestelldispositionsItemClicked
is checking the CTS member variable directly, which is changed after the old one is cancelled.
I strongly recommend following the standard cancellation pattern for .NET. This pattern means that cancelled code should not return; it should throw OperationCanceledException
. This is most easily done by replacing all IsCancellationRequested
checks with calls to ThrowIfCancellationRequested
. Also, all cancelable code should use (a copy of) the CancellationToken
rather than checking the CancellationTokenSource
directly.
E.g.:
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;
}
}