为 Blazor 中的嵌套(不是第一个或最后一个)方法维护 SynchronizationContext

Maintaining SynchronizationContext for nested (not first or last) methods in Blazor

在 .NetCore 3.1 中创建新的 Blazor 服务器端项目时,使用加载 Visual Studio 2019 的默认项目,请考虑以下示例(对他们自己的计数器示例页面略有更改):

<button class="btn btn-primary" @onclick="SingleMethodUpdate">Click Me</button>
<label>Update from same method: @result</label>
<button class="btn btn-primary" @onclick="NestedMethodUpdate">Click Me</button>
<label>Update from nested method: @nestedResult</label>

代码隐藏看起来像这样:

int result = 0;
int nestedResult = 0;
int nestedCounter = 0;

protected async Task SingleMethodUpdate()
{
    result++;
}

protected async Task NestedMethodUpdate()
{
    await NestedUpdate(++nestedCounter);
    await Task.Delay(1000);
    await NestedUpdate(++nestedCounter);
    await Task.Delay(1000);
    await NestedUpdate(++nestedCounter);
}

protected Task NestedUpdate(int update)
{
    nestedResult = update;
    return Task.FromResult(0);
}

第一个按钮和标签很简单,按一个按钮,UI 上的计数会按预期立即更新。不过第二个按钮比较复杂

中间的 await NestedUpdate(++nestedCounter) 调用没有反映在 UI 上。第一次调用和最后一次调用(第一次和最后一次嵌套方法调用)都实时反映在UI上,所以你看到的是:

Update from nested method: 1
*2 seconds go by
Update from nested method: 3

我意识到这是因为 SynchronizationContext 的警告,但我更好奇我将如何着手捕获和 维护 该上下文,以便每个 awaited 在我的按钮按下事件中调用的方法使用相同的上下文,因此 UI 将在代码执行时实时更新。有 nice/pretty 的方法吗?

I realize this is because of the caveats with SynchronizationContext

不,不是真的。所有这些代码都应该 运行 异步但在主线程上顺序执行。
此处的 SyncContext 没有任何问题或需要修复。

您看到的是由于 StateHasChanged() 逻辑,它实际上是关于一个状态,大概只是一个布尔标志。

Blazor 框架将在调用您的事件处理程序之前和之后设置此标志,但在中间您必须自己这样做。

快速修复和解释:

protected async Task NestedMethodUpdate()
{
    // the flag is pre-set here
    await NestedUpdate(++nestedCounter);
    // the thread is available and Blazor will render '1' and reset the flag
    await Task.Delay(1000);  

    await NestedUpdate(++nestedCounter);
    // the thread is available but the flag is down: the '2' is not shown
    await Task.Delay(1000);

    await NestedUpdate(++nestedCounter);
    StatehasChanged();  // <<-- add this
    // the thread is available and the flag is up: the '3' is shown
    await Task.Delay(1000);

    // you can add more steps
    await NestedUpdate(++nestedCounter);
    StatehasChanged();  // <<-- set the flag
    await Task.Delay(1000);  // show '4'

    // this result will show '5' after the method has completed
    await NestedUpdate(++nestedCounter);
}