使用WinForms. NET 8表单中的BlazorWebView控件(introduction),我确实显示了这个"Counter. razour"文件:

<p><button @onclick="IncrementCount">Increment counter</button></p>
<p>@_counterValue</p>

@code
{
    private int _counterValue = 0;

    private void IncrementCount()
    {
        _counterValue++;
    }
}

enter image description here

正确行为

上面的代码很完美:

  1. 用户点击按钮.
  2. 变量递增.
  3. 页面显示新的变量值.

enter image description here

不正确的行为

现在我把IncrementCount()方法改为这样:

private void IncrementCount()
{
    Synchronization上下文.Current!.Post(
        delegate
        {
            _counterValue++;
        }, 
        null);
}

现在它的行为像这样:

  1. 用户点击按钮.
  2. 变量递增(现在为"1").
  3. 页面仍显示"0".
  4. 用户再次点击按钮.
  5. 变量再次递增.(现在是"2").
  6. 页面显示"1".

也就是说,我总是会有一个错误.

修复它

为了解决这个问题,我必须将代码更改为:

private void IncrementCount()
{
    Synchronization上下文.Current!.Post(
        delegate
        {
            _counterValue++;
            StateHasChanged();
        }, 
        null);
}

现在,在添加一个对StateHasChanged()的调用之后,它的行为就像预期的那样:

  1. 用户点击按钮.
  2. 变量递增.
  3. 页面显示新的变量值.

上下文

以上Synchronization上下文的用法是一个最小的例子,我必须通过这个机制分派调用(主要是因为I have to call other WinForms dialogs).

我确实想理解为什么需要调用"StateHasChanged",因为我想避免它.

在我的实际应用程序中(不是上面最小的例子),这将迫使大量的重新渲染以适合应用程序.

我的问题

有人能解释一下为什么我直接改变变量和改变它通过Synchronization上下文的行为不同吗?

有没有改变,使它工作,没有额外的呼叫StateHasChanged()


更新1

在我的现实应用程序中,我大致执行以下步骤:

  1. 用户单击Blazor按钮.
  2. 显示了WinForms表单.
  3. 关闭表单后,我必须更新Blazor中的一些值.

所以这就是我在Synchronization上下文.Current!.Post()调用中执行代码的原因,建议here.

我还try 将代码放入队列中,并在WinForms应用程序的Application.Idle事件处理程序中处理此队列,但其行为是一样的(off—by—one错误).

推荐答案

IncrementCounter由渲染器的UI事件进程发布到Synchronization Context. 它在ComponentBase UI处理程序中作为callback传递.

Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
{
    var task = callback.InvokeAsync(arg);
    var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
        task.Status != TaskStatus.Canceled;

    // After each event, we synchronously re-render (unless !ShouldRender())
    // This just saves the developer the trouble of putting "StateHasChanged();"
    // at the end of every event callback.
    StateHasChanged();

    return shouldAwaitTask ?
        CallStateHasChangedOnAsyncCompletion(task) :
        Task.CompletedTask;
}

完整代码可以在这里找到:https://github.com/dotnet/aspnetcore/blob/63c8031b6a6af5009b3c5bb4291fcc4c32b06b10/src/Components/Components/src/ComponentBase.cs#L322

所以你要做的就是把这个

{
    _counterValue++;
} 

作为代码块进入IncrementCounter后面的Synchronization Context队列. IncrementCounter在代码执行之前完成所有的渲染,并改变_counterValue.

当它被直接更改时,在UI处理程序中调用StateHasChanged之前发生变化.

问:为什么要直接把代码发到Synchronization Context

ComponentBase具有内置函数InvokeAsync来调用必须在Synchronization Context上执行的任何代码 参见—https://github.com/dotnet/aspnetcore/blob/63c8031b6a6af5009b3c5bb4291fcc4c32b06b10/src/Components/Components/src/ComponentBase.cs#L178

注[个人]:

我现在总是在改变:

private void IncrementCount()
{ }

对此:

private Task IncrementCount()
{ 
    //work
    return Task.CompletedTask;
}

它阻止Visual Studio在您键入await时自动执行此操作:

private async void IncrementCount()
{ }

最新情况:

根据您的更新,您需要在posted [anonymous]方法中调用StateHasChanged.

请注意,您可以在组件中禁用所有对StateHasChanged的自动调用,如下所示:

@page "/counter"
@implements IHandleEvent

@code {
    Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
    {
        return callback.InvokeAsync(arg);
    }
}

只有在需要的时候才打StateHasChanged.

Csharp相关问答推荐

自定义JsonEditor,用于将SON序列化为抽象类

如何模拟耐久任务客户端在统一测试和获取错误在调度NewsListationInstanceAsync模拟设置

可为空的泛型属性

更新产品但丢失产品ASP.NET Core的形象

XUNIT是否使用测试数据的源生成器?

在实体框架中处理通用实体&S变更跟踪器

如何在C#中将方法/线程启动传递给基本构造函数

依赖项注入、工厂方法和处置困境

如何在CSharp中将json字符串转换为DataTable?

异步等待Foreach循环中的ConfigureAWait(FALSE)执行什么操作?

是否可以在Entity Framework Core中使用只读 struct 作为拥有实体?

如何使用.NET Aspire从Blazor应用程序与GRPC API通信?

KeyDown从我的文本框中删除输入,如何停止?

当`JToken?`为空时?

使用C#代码和SQL SERVER中的相同证书签名会产生不同的结果

.NET文档对继承的困惑

反序列化我以前使用System.Text.Json序列化的文件时出现异常

使用ITfoxtec.Identity.Saml2解析相同键多值SAML 2声明

身份验证中间件如何处理多个方案

初始化具有EntityFrameworkCore和不同架构的数据库时引发System.MissingMethodExcept