c# - 避免为异步方法重新创建事件委托
问题描述
我有一个 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"
这将再次重新创建代表。我该如何为异步方法做到这一点?
解决方案
首先,做这样的事情
@onclick="async () => await SetCurrentAsync(localI)"
是不必要的。您将任务包装在任务中。
@onclick="()=> SetCurrentAsync(localI)"
工作原理完全相同。Blazor 组件内部事件处理程序(用于按钮单击,...)包装您在任务中传递的任何操作。最简单的是它看起来像这样:
var task = InvokeAsync(EventMethod);
StateHasChanged();
if (!task.IsCompleted)
{
await task;
StateHasChanged();
}
您应该始终使用Func<Task>
来处理sync Task
和Task
。将 aAction
与异步方法一起使用是否 - 它会将 a 返回void
到 Blazor 组件事件处理程序。
有关工作演示,请参阅下面的代码页。三个按钮使用该模式
- 首先使用异步任务调用产生任务方法。
- 其次调用一个简单的Task方法。
- 使用
Action
withasync 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 个编辑和查看按钮。我从来没有注意到一个问题。
推荐阅读
- html - 如何更改语义 UI 下拉菜单的边框样式?
- mysql - 如何在特定条件下连接 5 个表 MYSQL
- javascript - Firestore onSnapshot - firebase.firestore 不是函数 - 无法解析模块说明符“firebase”
- python - 总是将最后一张图像返回到标签中
- python - Python setuptools ssl 安装选项无法识别
- postgresql - GORM 在 postgresql 中更改时间格式
- angular - Angular 上的哨兵在本地主机上工作(服务)但不在服务器上(构建)
- sql - 在 SQL*PLUS 中创建新配置文件时出错
- python - Dask DataFrame 过滤器和重新分区提供了一些空分区
- google-cloud-platform - 如何在 Firestore 的子集合名称中使用通配符?