第二个活动劫持了第一个处理程序

发布于 2025-01-21 21:57:46 字数 4283 浏览 0 评论 0原文

我有一个带有两个按钮,可以执行相同的基础服务类别方法。当该方法运行时,它会在某些数据上进行迭代,以进行富集,并在迭代中发出一个事件,该事件会处理每个按钮更新进度条的情况。

我已经将服务事件处理程序设置为@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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

揽清风入怀 2025-01-28 21:57:46

我认为您的基本方法是有缺陷的。让我解释一下:

  • 单击一个
  • 挂钩处理程序A到事件
  • getDataAsync(a)
  • 发生了一些循环,但不是完整的,但是
  • 单击b
  • 连接处理程序b to event
  • 呼叫
  • call getDataAsync(b
  • ) Handler B Fire(我不认为您打算!)
    ....

您正在使用同一事件处理程序,用于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();
          });
    }
}

I think your basic approach here is flawed. Let me explain:

  • Click A
  • Hook up Handler A to Event
  • Call GetDataAsync(A)
  • Some loops happen, but are not complete
  • Click B
  • Hook up Handler B to Event
  • Call GetDataAsync(B)
  • GetDataAsync(B) loops and fires Event
  • Both Handler A and Handler B fires (I don't think you intend that!)
    ....

You are using the same Event Handler for both calls to GetDataAsync. When both GetDataAsync 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:

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 = 
quot;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 ;
}

Demo Page:

@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();
          });
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文