第二个活动劫持了第一个处理程序
我有一个带有两个按钮,可以执行相同的基础服务类别方法。当该方法运行时,它会在某些数据上进行迭代,以进行富集,并在迭代中发出一个事件,该事件会处理每个按钮更新进度条的情况。
我已经将服务事件处理程序设置为@Click
事件方法中的本地功能。该本地函数处理目标元素的进度和JS Interop以更新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;
}
I have a Blazor component with two buttons that execute the same underlying service class async method. As that method operates, it iterates on some data for enrichment and emits an event per iteration that the component handles to update a progress bar per button.
I've set the service event handlers up as local functions within the @click
event method. This local function handles the target element's counters for progress and the JS interop to update the UI. This all works as expected when clicked individually and allowed to complete before clicking the other button. The problem is that if both buttons are clicked fast enough together, whereby the first operation is still processing, the second event handler hijacks the first and all callbacks flow to it. The first progress bar idles and the second one goes over 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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我认为您的基本方法是有缺陷的。让我解释一下:
....
您正在使用同一事件处理程序,用于
getDataAsync
。当两个getDataAsync
正在运行的方法都从两者中获取事件。根据第二次点击的时间安排,事件的测序变得非常混乱。异步行为并不总是一门精确的科学,您可能处于边缘领域。我认为这是由于线程任务调度程序优先任务的方式引起的(但这不过是一个受过教育的猜测,我可能完全错了!)。我不会说有一个错误,但是...它需要更多的调查。
更新
以下是您的代码的简化版本,该版本使用回调进行隔离:
演示页面:
I think your basic approach here is flawed. Let me explain:
....
You are using the same Event Handler for both calls to
GetDataAsync
. When bothGetDataAsync
methods are running each handler is getting the events from both.The sequencing of events gets pretty confusing based on the timing of the second click. Async behaviour is not always an exact science and you may be in edge territory. I think this is caused by the way the Thread Task Scheduler prioritises it's tasks (but that's no more than an educated guess and I may be totally wrong!). I won't go as far as saying there's a bug, but... it needs more investigation.
Update
Here's a simplified version of your code that uses callbacks for isolation:
Demo Page: