У меня есть компонент Blazor, который должен отображать данные длительной операции. По этой причине я показываю счетчик, но поскольку это занимает много времени, я хочу иметь возможность отменить эту загрузку, когда, например, пользователь уходит (например, пользователь нажимает кнопку входа в систему во время загрузки данных).
Я реализовал шаблон Dispose с объектом CancellationTokenSource в моем компоненте, я сделал свою функцию асинхронной с параметром Token as, но кажется, что 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">
@[email protected]
</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, которые не справляются с этим.
@implements IDisposable
и метод Dispose добавляются непосредственно в код компонента, а не в класс модели. Это то, на что стоит обратить внимание? - person StriplingWarrior   schedule 16.07.2021await Task.Run(() => CommercialOfferService.GetRecentAsync(cancellationToken: cts.Token), cts.Token);
, теперь он бросает - person exrezzo   schedule 20.07.2021