首页 > 解决方案 > 避免为异步方法重新创建事件委托

问题描述

我有一个 Blazor Server 应用程序,其中许多按钮在 for 循环中呈现。:

@for (int i = 0; i < _navItems.Count; i++)
{
  var localI = i;
  <div class="col-3 mb-2">
     <button @onclick="async () => await SetCurrentAsync(localI)" class="btn btn-sm">
       @(i + 1)
     </button>
  </div>
}

但是,此处的 Microsoft Docs 不建议使用此方法,因为@onclick每次呈现组件时都会重新创建中指定的委托:

Blazor 为循环中的元素或组件重新创建 lambda 表达式委托可能会导致性能下降。

此后文档中提供的解决方案(以及链接的 GitHub 问题中也提供了一个具有包含委托Button的属性的类型:Action

@foreach (var button in _buttons)
{
  <div class="col-3 mb-2">
   <button @key="button.Id" @onclick="button.Action" class="btn btn-sm">
    @(i + 1)
   </button>
  </div>
}

@code {
   
     List<Button> _buttons = new();
     List<NavItem> _items;

     protected override async Task OnInitializedAsync()
     {
        _items = await GetItemsFromDb();
        for(int i = 0; i < _items.Count; i++)
        {
            var localI = i;
            _buttons.Add(new Button 
            { 
               Id = item.Id, 
               Action = () => SetCurrent(localI);  
            });
        }
     }

    class Button 
    {
         public int Id { get; set; }
         
         public Action Action { get; set; }
    }
}

现在,@onclick引用Button.Action并解决了委托娱乐问题。

SetCurrent在不异步 之前,这一切都很有趣和游戏。Action必须更改为Func<Task> ,并且必须使用异步 lambda 表达式添加按钮:

_buttons.Add(new Button 
{ 
  Id = item.Id, 
  Action = async () => await SetCurrentAsync(localI);  
});

我仍然必须这样做:

@onclick="async() => await button.Action"

这将再次重新创建代表。我该如何为异步方法做到这一点?

标签: c#blazorblazor-server-side

解决方案


首先,做这样的事情

@onclick="async () => await SetCurrentAsync(localI)"

是不必要的。您将任务包装在任务中。

@onclick="()=> SetCurrentAsync(localI)"

工作原理完全相同。Blazor 组件内部事件处理程序(用于按钮单击,...)包装您在任务中传递的任何操作。最简单的是它看起来像这样:

var task = InvokeAsync(EventMethod);
StateHasChanged();
if (!task.IsCompleted)
{
    await task;
    StateHasChanged();
}

您应该始终使用Func<Task>来处理sync TaskTask。将 aAction与异步方法一起使用是否 - 它会将 a 返回void到 Blazor 组件事件处理程序。

有关工作演示,请参阅下面的代码页。三个按钮使用该模式

  1. 首先使用异步任务调用产生任务方法。
  2. 其次调用一个简单的Task方法。
  3. 使用Actionwithasync void并演示 UI 更新问题。

关键是前两个都将 a 返回Task到 Blazor 组件事件处理程序,以便它可以正确处理组件呈现。

@page "/"
<h3>Button Actions</h3>

@foreach (var item in _buttonActions)
{
    <div class="m-2">
        <button class="btn btn-secondary" @onclick="item.Action">@item.Title</button>
    </div>
}
<div>
    @message
</div>

@code {
    private List<ButtonAction> _buttonActions = new List<ButtonAction>();

    protected override void OnInitialized()
    {
        {
            var item = new ButtonAction() { Title = "Task" };
            item.Action = () => GetValue(item);
            _buttonActions.Add(item);
        }
        {
            var item = new ButtonAction() { Title = "Async Task" };
            item.Action = () => GetValueAsync(item);
            _buttonActions.Add(item);
        }
        {
            var item = new ButtonAction() { Title = "Async Void Task" };
            item.MyAction = () => GetValueVoidAsync(item);
            _buttonActions.Add(item);
        }
    }

    private string message;

    public Task GetValue(ButtonAction item)
    {
        message = $"Value: {item.Id}";
        return Task.CompletedTask;
    }

    public async Task GetValueAsync(ButtonAction item)
    {
        await Task.Yield();
        message = $"Value: {item.Id}";
    }

    public async void GetValueVoidAsync(ButtonAction item)
    {
        await Task.Yield();
        message = $"Value: {item.Id}";
    }

    public class ButtonAction
    {
        public string Title { get; set; }
        public Guid Id { get; } = Guid.NewGuid();
        public Func<Task> Action { get; set; }
        public Action MyAction { get; set; }
    }
}

在性能方面,我认为这真的取决于“多少?”。我不将模式用于编辑列表,因为我可能有 25 个编辑和查看按钮。我从来没有注意到一个问题。


推荐阅读