问题描述
我最近就Blazor wasmhere中的await Task.Run(StateHasChanged);
和await InvokeAsync(StateHasChanged);
之间的区别提出了一个问题。
结论是await Task.Run(StateHasChanged);
是不正确的,应该避免;使用它会产生与await InvokeAsync(StateHasChanged);
相同的结果,但是当线程可用时会出现问题(公认的答案会详细说明)。
我已将我的代码库更新为使用await InvokeAsync(StateHasChanged);
,但是我发现两者的结果实际上存在差异。
以下是我的应用程序中问题的最小复制:
家长
<h1>Parent: @title</h1>
<button class="btn btn-primary" @onclick="() => SetBool(true)">True</button>
<button class="btn btn-primary" @onclick="() => SetBool(false)">False</button>
<Child Bool="@Bool" @ref="Child"></Child>
@code {
private bool Bool = false;
private Child Child;
private string title;
private async void SetBool(bool name)
{
Bool = name;
title = Bool ? "True" : "False";
// NOTE: This will work as expected; Child will be updated with the Parent
// await Task.Run(StateHasChanged);
// NOTE: This will only update the Child the second time it is clicked
await InvokeAsync(StateHasChanged);
await Child.Trigger();
}
}
子级
<h3>Child: @title</h3>
@code {
[Parameter]
public bool Bool { set; get; } = false;
private string title;
public async Task Trigger()
{
title = Bool ? "True" : "False";
await InvokeAsync(StateHasChanged);
}
}
单击父项中的True
或False
按钮应同时更新父项和子项中的Bool
值。请注意,title
变量仅用于可视化显示Bool
值。使用await Task.Run(StateHasChanged);
将导致父对象和子对象的状态同时更新。另一方面,await InvokeAsync(StateHasChanged);
将更新父组件,但不会更新子组件;需要单击两次a按钮才能获得子组件中各自的值。
我将此值传递给子组件的方式有问题吗?
请注意,不能使用await Task.Run(StateHasChanged);
;这样做意味着我无法使用bUnit测试组件。
复制代码可用here。
推荐答案
以下代码片段介绍了应该如何编码:
Parent.razor
<h1>Parent: @title</h1>
<button class="btn btn-primary" @onclick="() => SetBool(true)">True</button>
<button class="btn btn-primary" @onclick="() => SetBool(false)">False</button>
<Child Bool="@Bool" @ref="Child"></Child>
@code {
private bool Bool = false;
private Child Child;
private string title;
protected override void OnInitialized()
{
title = Bool ? "True" : "False";
}
private async Task SetBool(bool name)
{
Bool = name;
title = Bool ? "True" : "False";
await Task.CompletedTask;
}
}
Child.razor
<h3>Child: @title</h3>
@code {
[Parameter]
public bool Bool { set; get; } = false;
private string title;
private bool _Bool;
public async Task Trigger()
{
title = _Bool ? "True" : "False";
await InvokeAsync(StateHasChanged);
}
protected override async Task OnParametersSetAsync()
{
_Bool = Bool;
await Trigger();
}
}
注意:您面临的问题不是因为使用Task.Run(StateHasChanged); and await InvokeAsync(StateHasChanged);
,尽管结果不同。您根本没有遵循Blazor组件模型规则。您是否意识到,当您单击
SetBool
按钮时,父组件将重新呈现,因为它的状态已更改。因此,子组件使用Bool==true重新呈现,但是您在屏幕上看不到这一点,因为更改子组件中的标题的代码被放在Trigger方法中,该方法仅从父组件调用。您的假设是错误的。请勿修改或更改Bool参数属性的状态。定义一个局部变量来存储参数属性的值,您可以随心所欲地操作其值。换句话说,零部件参数特性应定义为自动特性.它们是Blazor框架将值从父组件传递到其子组件的DTO。
不要不必要地从UI事件处理程序调用StateHasChanged方法。自动调用。
请勿使用
async void
.使用async Task.
当您使用async void
时,Blazor不会计算异步方法何时完成,因此它不会调用StateHasChanged方法避免使用
Task.Run
。我从来不用。我甚至不记得为什么我不应该用它吧。我就是不用它。
这篇关于Blazor StateHasChanged和子参数(`等待Task.Run(StateHasChanged);`vs`等待InvokeAsync(StateHasChanged);`)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!