CancellationToken 永远不会取消我长时间运行的加载数据功能
Posted
技术标签:
【中文标题】CancellationToken 永远不会取消我长时间运行的加载数据功能【英文标题】:CancellationToken never cancel on my long running loading data function 【发布时间】:2021-09-25 09:00:06 【问题描述】:我有一个 Blazor 组件,它必须显示来自长时间运行的操作的数据。出于这个原因,我显示了一个微调器,但由于它需要很长时间,我希望能够在例如用户导航离开时取消此加载(例如,用户在加载数据时单击登录)。
我在组件中使用 CancellationTokenSource 对象实现了 Dispose 模式,我使用 Token 作为参数使我的函数异步,但似乎在我的加载数据函数中,令牌的“IsCanceled”从未设置为 true,也不会引发 OperationCanceledException .如果我使用一个只等待 Task.Delay 20 秒的虚拟函数进行测试,并且我传递了令牌,则它被正确取消。我做错了什么?
最终结果是,当数据正在加载并且微调器正在显示时,如果用户单击按钮以导航离开,它会等待数据加载完成。
我显示数据的视图; “LoadingBox”在未创建列表时显示微调器。
<Card>
<CardHeader><h3>Ultime offerte</h3></CardHeader>
<CardBody>
<div class="overflow-auto" style="max-height: 550px;">
<div class="@(offersAreLoading ?"text-danger":"text-info")">LOADING: @offersAreLoading</div>
<LoadingBox IsLoading="lastOffers == null">
@if (lastOffers != null)
@if (lastOffers.Count == 0)
<em>Non sono presenti offerte.</em>
<div class="list-group list-group-flush">
@foreach (var off in lastOffers)
<div class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">
<a href="@(NavigationManager.BaseUri)offerta/@off.Oarti">
@off.CodiceOfferta4Humans-@off.Versione
</a>
</h5>
<small>@((int) ((DateTime.Now - off.Created).TotalDays)) giorni fa</small>
</div>
<p class="mb-1"><em>@(off.OggettoOfferta.Length > 50 ? off.OggettoOfferta.Substring(0, 50) + "..." : off.OggettoOfferta)</em></p>
<small>@off?.Redattore?.Username - @off.Created</small>
</div>
</div>
</LoadingBox>
</div>
</CardBody>
</Card>
组件代码隐藏。在这里,我调用了长时间运行的函数 (GetRecentAsync),当用户离开或执行其他操作时,我想取消该函数:
public partial class Test : IDisposable
private CancellationTokenSource cts = new();
private IList<CommercialOffer> lastOffers;
private bool offersAreLoading;
[Inject] public CommercialOfferService CommercialOfferService get; set;
async Task LoadLastOffers()
offersAreLoading = true;
await InvokeAsync(StateHasChanged);
var lo = await CommercialOfferService.GetRecentAsync(cancellationToken: cts.Token);
lastOffers = lo;
offersAreLoading = false;
await InvokeAsync(StateHasChanged);
async Task fakeLoad()
offersAreLoading = true;
await InvokeAsync(StateHasChanged);
await Task.Delay(TimeSpan.FromSeconds(20), cts.Token);
offersAreLoading = false;
await InvokeAsync(StateHasChanged);
protected override async Task OnAfterRenderAsync(bool firstRender)
if (firstRender)
await LoadLastOffers();
await base.OnAfterRenderAsync(firstRender);
public void Dispose()
cts.Cancel();
cts.Dispose();
public async Task<List<CommercialOffer>> GetRecentAsync(CancellationToken cancellationToken)
try
cancellationToken.ThrowIfCancellationRequested();
var result = await _cache.GetOrCreateAsync<List<CommercialOffer>>("recentOffers", async entry =>
entry.AbsoluteExpiration = DateTimeOffset.Now.Add(new TimeSpan(0, 0, 0, 30));
var list = await _unitOfWork.CommercialOfferRepository.GetAllWithOptionsAsync();
foreach (var commercialOffer in list)
// sta operazione è pesante, per questo ho dovuto cachare
// BOTH ISCANCELLATIONREQUESTED AND THROWIFCANCELLATINREQUESTED DOES NOT WORK, ISCANCELLATIONREQUESTED IS ALWAYS FALSE.
cancellationToken.ThrowIfCancellationRequested();
if (cancellationToken.IsCancellationRequested) return new List<CommercialOffer>();
await _populateOfferUsersAsync(commercialOffer);
return list.Take(15).OrderByDescending(o => o.Oarti).ToList();
);
return result;
catch (OperationCanceledException)
// HERE I SET A BREAKPOINT IN ORDER TO SEE IF IT RUNS, BUT IT DOESN'T WORK
谢谢!
编辑 20/07/2021
感谢@Henk Holterman。 GetRecentAsync 获取所有最近的商业报价,由一个简单的表格编译,并有一些数据作为通常的用例。 这些商业报价中的每一个都涉及 4 个用户(他们管理报价、上级、批准者等),我为每个这些用户填充了一个 foreach 循环,用于我想要显示的每个商业报价。
我知道我应该从一开始就从 SQL 查询中创建整个实体(商业报价),但我需要这样做以解决顺序问题和关注点分离问题。
因此,_populateOfferUsersAsync(commercialOffer) 查询某个商品的 4 个用户,创建这 4 个实体并将它们分配给该商品:
private async Task _populateOfferUsersAsync(CommercialOffer commercialOffer)
commercialOffer.Responsabile = await _unitOfWork.UserRepository.GetByIdAsync(commercialOffer.IdResponsabile);
commercialOffer.Redattore = await _unitOfWork.UserRepository.GetByIdAsync(commercialOffer.IdRedattore);
commercialOffer.Approvatore = await _unitOfWork.UserRepository.GetByIdAsync(commercialOffer.IdApprovatore);
commercialOffer.Revisore = await _unitOfWork.UserRepository.GetByIdAsync(commercialOffer.IdRevisore);
在后台我使用 Dapper 进行数据库查询:
public async Task<User> GetByIdAsync(long id)
var queryBuilder = _dbTransaction.Connection.QueryBuilder($@"SELECT * FROM GEUTENTI /**where**/");
queryBuilder.Where($"CUSER = id");
queryBuilder.Where($"BSTOR = 'A'");
queryBuilder.Where($"BDELE = 'S'");
var users = await queryBuilder.QueryAsync<User>(_dbTransaction);
return users.FirstOrDefault();
据我所见,没有简单有效的方法来传递 CancellationToken 来停止 Dapper 查询,可能是我或 Dapper 不适合这些东西
【问题讨论】:
我对 Blazor 不是很熟悉,但我注意到在 this article 中,@implements IDisposable
和 Dispose 方法直接添加到组件代码中,而不是模型类中。有什么需要调查的吗?
好的,我错过了 Dapper 角度。这是你最重要的标签。
Ans 看到这个***.com/q/25540793/60761
让我们考虑一下 Dapper 可以使用令牌(实际上不是很好)但是我无法停止 GetRecentAsync 内的循环这一事实并没有使感觉。在那种情况下,我正在从 db 读取数据,因此没有必要取消 sql 事务(如果我正在写入 db,那将是一个不同的故事,在这种情况下,我想我将不得不回滚事务)。我现在取得的最好成绩是使用await Task.Run(() => CommercialOfferService.GetRecentAsync(cancellationToken: cts.Token), cts.Token);
现在它抛出
【参考方案1】:
我做错了什么?
将您的取消令牌转发到所有异步 I/O 方法很重要:
// var list = await _unitOfWork.CommercialOfferRepository.GetAllWithOptionsAsync();
var list = await _unitOfWork.CommercialOfferRepository.GetAllWithOptionsAsync(cancellationToken);
然后当然要相应地修改GetAllWithOptionsAsync()
。实体框架中的所有异步方法都有一个接受CancellationToken
的重载。
...导航离开它等待数据加载完成。
当 GetAllWithOptionsAsync() 占用大部分时间时,这就是数字。下一个 foreach 循环应该在取消时中断,但这可能并不明显。
不过,_populateOfferUsersAsync(commercialOffer)
也应该将 CancellationToken 作为参数。
从您自己的 FakeLoad() 中可以看出,Blazor 和 CancellationTokenSource 没有损坏。
【讨论】:
不幸的是,即使我将令牌传递给后续函数,例如 _populateOfferUsersAsync(commercialOffer) 我也无法获得 OperationCanceledException,isCancellationRequested == true 也无法在任何地方获得,似乎这些函数中的令牌(从 GetAllWithOptionsAsync 开始)从未设置为取消,就像它是另一个对象或者我不知道是什么 您能发布 GetAllWithOptionsAsync 和 _populateOfferUsersAsync 的大纲吗?它们有多异步? 我用更多信息编辑了我的问题以上是关于CancellationToken 永远不会取消我长时间运行的加载数据功能的主要内容,如果未能解决你的问题,请参考以下文章
C#:使用 CancellationToken 取消 MySqlCommand,给出 NULLReferenceException
使用 CancellationToken 取消任务而不在任务中明确检查?
取消 HttpClient 请求 - 为啥 TaskCanceledException.CancellationToken.IsCancellationRequested 为假?