避免为异步方法重新创建事件委托
Avoid event delegate recreation for async methods
我有一个 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 here 不推荐这种方法,因为每次呈现组件时都会重新创建 @onclick
中指定的委托:
Blazor's recreation of lambda expression delegates for elements or components in a loop can lead to poor performance.
此后文档中提供的解决方案(以及 linked GitHub issue 中提供的解决方案是创建一个 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
。将 Action
与异步方法一起使用是不行的 - 它 return 是 Blazor 组件事件处理程序的 void
。
有关工作演示,请参阅下面的代码页。共有三个使用模式
的按钮
- 首先使用异步任务调用一个产生任务的方法。
- 第二次调用一个简单的任务方法。
- 使用
Action
和 async void
并演示 UI 更新问题。
关键是前两个都 return 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 个编辑和查看按钮。我从来没有注意到一个问题。
我有一个 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 here 不推荐这种方法,因为每次呈现组件时都会重新创建 @onclick
中指定的委托:
Blazor's recreation of lambda expression delegates for elements or components in a loop can lead to poor performance.
此后文档中提供的解决方案(以及 linked GitHub issue 中提供的解决方案是创建一个 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
。将 Action
与异步方法一起使用是不行的 - 它 return 是 Blazor 组件事件处理程序的 void
。
有关工作演示,请参阅下面的代码页。共有三个使用模式
的按钮- 首先使用异步任务调用一个产生任务的方法。
- 第二次调用一个简单的任务方法。
- 使用
Action
和async void
并演示 UI 更新问题。
关键是前两个都 return 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 个编辑和查看按钮。我从来没有注意到一个问题。