【发布时间】: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 角度。这是你最重要的标签。
-
让我们考虑一下 Dapper 可以使用令牌(实际上不是很好)但是我无法停止 GetRecentAsync 内的循环这一事实并没有使感觉。在那种情况下,我正在从 db 读取数据,因此没有必要取消 sql 事务(如果我正在写入 db,那将是一个不同的故事,在这种情况下,我想我将不得不回滚事务)。我现在取得的最好成绩是使用
await Task.Run(() => CommercialOfferService.GetRecentAsync(cancellationToken: cts.Token), cts.Token);现在它抛出
标签: c# asynchronous blazor dapper cancellationtokensource