我有一个Blazor组件,其中有两个按钮执行相同的底层服务类异步方法。当该方法运行时,它迭代一些数据以进行充实,并在每次迭代时发出一个事件,组件处理该事件以更新每个按钮的进度条。
我已经将服务事件处理程序设置为@click
事件方法中的本地函数。这个局部函数处理目标元素的进度计数器和JS互操作来更新UI。当单独单击并允许在单击另一个按钮之前完成时,这一切都像预期的那样工作。问题是,如果两个按钮同时点击的速度足够快,那么第一个操作仍在处理中,第二个事件处理程序劫持了第一个操作,并且所有回调都流到它。第一个进度条空闲,第二个进度条超过100%。
Component.razor
@inject IFooService FooService
<a class="btn btn-light p-1 @(isExcelProcessing ? "disabled" : null) " role="button" @onclick="ExportExcel">
<i class="fa fa-file-excel-o text-dark fa-2x"></i><br>
<span class="small text-nowrap">
Summary Excel
@if (isExcelProcessing)
{
<div class="progress" style="height: 15px">
<div id="excelProgressBar" ></div>
</div>
}
</span>
</a>
<a class="btn btn-light @(isTextProcessing ? "disabled" : null) " role="button" @onclick="ExportText">
<i class="fa fa-2x fa-file-text-o text-dark"></i><br />
<span class="small text-nowrap">
Instruction Text
@if (isTextProcessing)
{
<div class="progress" style="height: 15px">
<div id="textProgressBar" ></div>
</div>
}
</span>
</a>
@code {
private bool isExcelProcessing = false;
private bool isTextProcessing = false;
private async Task ExportExcel()
{
isExcelProcessing = true;
var excelIssuesCounter = 0;
var excelProcessingComplete = 0m;
var itemsToEnrichCount = (await FooService.GetItemsToEnrich()).ToArray();
void OnFetched(object sender, EventArgs args)
{
excelIssuesCounter++;
excelProcessingComplete = (Convert.ToDecimal(excelIssuesCounter) / itemsToEnrichCount) * 100;
JS.InvokeVoidAsync("updateProgress",
"excelProgressBar",
excelProcessingComplete);
}
FooService.OnFetched += OnFetched;
// this method has the iterations that call back to the defined event handler
var fooData = await FooService.GetDataAsync("excel");
// do other stuff like building a model,
// render a template from razor engine
// download the file
isExcelProcessing = false;
FooService.OnFetched -= OnIssueFetched;
}
private async Task ExportText()
{
isTextProcessing = true;
var textIssuesCounter = 0;
var textProcessingComplete = 0m;
var itemsToEnrichCount = (await FooService.GetItemsToEnrich()).ToArray();
void OnFetched(object sender, EventArgs args)
{
textIssuesCounter++;
textProcessingComplete = (Convert.ToDecimal(textIssuesCounter) / itemsToEnrichCount) * 100;
JS.InvokeVoidAsync("updateProgress",
"textProgressBar",
textProcessingComplete);
}
FooService.OnFetched += OnFetched;
// this method has the iterations that call back to the defined event handler
var fooData = await FooService.GetDataAsync("text");
// do other stuff like building a model,
// render a template from razor engine
// download the file
isTextProcessing = false;
FooService.OnFetched -= OnFetched;
}
}
FooService.cs
public event EventHandler OnFetched;
public async Task<FooData> GetDataAsync()
{
// not material to the problem. just here for example
var dataToEnrich = GetRawData();
var result = new FooData();
foreach(var itemToEnrich in dataToEnrich)
{
var enrichedFoo = await EnrichAsync(itemToEnrich);
result.EnrichedItems.Add(enrichedFoo);
// this is the material operation that doesn't always go to the desired handler
OnFetched?.Invoke(this, null);
}
return result;
}
我认为你的基本方法是有缺陷的。让我解释一下:
- 点击
- 连接处理程序A到事件
- 调用GetDataAsync (A)
- 一些循环发生,但不完整 B
- 点击
- 将处理程序B连接到事件
- 调用GetDataAsync (B)
- getdatasync (B)循环并触发Event
- 处理程序A和处理程序B都触发(我不认为你想这样!)…
您对GetDataAsync
的两个调用使用相同的事件处理程序。当两个GetDataAsync
方法都在运行时,每个处理程序都从两个方法中获取事件。
基于第二次点击的时间,事件的顺序变得非常混乱。异步行为并不总是一门精确的科学,你可能处于边缘地带。我认为这是由线程任务调度程序优先处理任务的方式引起的(但这只不过是一个有根据的猜测,我可能完全错了!)。我不会说有漏洞,但是…这需要更多的调查。
下面是使用回调进行隔离的代码的简化版本:
public class MyService
{
public async Task<SomeData> GetDataAsync(Action<SomeData> callback)
{
var result = new SomeData();
foreach (var item in data)
{
await Task.Delay(1000);
item.Value = $"updated at {DateTime.Now.ToLongTimeString()}";
callback.Invoke(result);
}
return result;
}
private List<SomeData> data => new List<SomeData> {
new SomeData { Key = "France" },
new SomeData { Key = "Portugal" },
new SomeData { Key = "Spain" },
};
}
public class SomeData
{
public string Key { get; set; } = String.Empty;
public string Value { get; set; } = String.Empty ;
}
演示页面:
@page "/"
@inject MyService myService
<div class="p-2">
<button class="btn btn-primary" @onclick=FirstClicked>First</button>
<div class="p-2">
Counter = @firstCounter;
</div>
</div>
<div class="p-2">
<button class="btn btn-info" @onclick=SecondClicked>First</button>
<div class="p-2">
Counter = @secondCounter;
</div>
</div>
@code {
int firstCounter = 0;
int secondCounter = 0;
private async void FirstClicked()
{
firstCounter = 0;
void firstHandler(SomeData data)
{
firstCounter++;
this.StateHasChanged();
}
var ret = await this.myService.GetDataAsync(firstHandler);
}
private async void SecondClicked()
{
secondCounter = 0;
var ret = await this.myService.GetDataAsync((data) =>
{
secondCounter++;
this.StateHasChanged();
});
}
}