【问题标题】:Calling async method from inside NavigationManager.LocationChanged从 NavigationManager.LocationChanged 内部调用异步方法
【发布时间】:2020-09-12 07:15:21
【问题描述】:

我正在使用NavigationManager.LocationChanged 来捕获查询字符串。获取查询字符串值后,我正在进行 ajax 调用,这是异步的。 LocationChanged 本身是同步方法,貌似没有LocationChanged 的异步版本。而且在LocationChanged内部调用async方法时,async方法设置的值会滞后一步。

这是再现:

@page "/investigate"
@implements IDisposable
@inject NavigationManager NM

<h1>Sync: @SyncValue</h1>
<h1>Async: @AsyncValue</h1>
<button @onclick="TriggerLocationChange">Increment</button>

@code {
    private string SyncValue;
    private string AsyncValue;
    private int Counter = 1;

    protected override void OnInitialized()
    {
        NM.LocationChanged += OnLocationChanged;
    }

    public void Dispose()
    {
        NM.LocationChanged -= OnLocationChanged;
    }

    private void OnLocationChanged(object sender, LocationChangedEventArgs args)
    {
        // sync action, just for comparison
        SyncValue = (Counter * 1000).ToString();

        DoSomeAsync();
    }

    private async Task DoSomeAsync()
    {
        // http call to server
        await Task.Delay(1);

        AsyncValue = (Counter * 1000).ToString();
    }

    private void TriggerLocationChange()
    {
        Counter++;
        NM.NavigateTo("investigate?counter=" + Counter);
    }
}

@AsyncValue@SyncValue 落后一步。

如何防止异步方法在从 LocationChanged 内部调用时滞后?

【问题讨论】:

    标签: blazor blazor-client-side


    【解决方案1】:

    经过大量试验和错误,我发现了以下内容:

    1. LocationChanged 运行时尚未设置路由参数值。这在我上面的示例中没有显示,但对我来说很重要。路由参数可以从 URL 中手动提取,或者我们可以等到 blazor 使用await Task.Delay(1) 填充路由参数。

    2. 在异步方法结束时调用StateHasChanged()

    3. According to the documentation 我们应该用base.InvokeAsync(() =&gt; ...) 结束通话

    经过这些修改,OnLocationChanged 变为:

    private void OnLocationChanged(object sender, LocationChangedEventArgs args)
    {
        // ...
    
        base.InvokeAsync(async () =>
        {
            await Task.Delay(1);  // wait for blazor to populate route parameters
            await DoSomeAsync();
            StateHasChanged();
        });
    }
    

    【讨论】:

    • 你对这些领域的观察很奇怪,我认为你误解了一些东西。 .ContinuWith() 构造是 async/await 的替代方案,但不太常用。
    • “当异步方法运行时,不要依赖对象字段来获得正确的值。”这是一个错误且毫无根据的陈述...... ContinueWith 的使用并没有真正增加任何价值或解决您面临的问题。您的代码未按预期工作:第一次单击不会产生视觉变化(查询字符串除外)。在第二次单击时,SyncValue 和 AsyncValue 显示值 2000,并且 Counter=2。我有一个新答案,其中变量和 Counter 的值是相同的。没有滞后...
    • 这里是 Blazor 新手。 Blazor 文档说“组件的生命周期方法和 Blazor 引发的任何事件回调都在同步上下文中执行。”所以你应该不需要包装 InvokeAsync 吧?
    • @pholly 实际上我在没有 InvokeAsync 的情况下进行了尝试,但它确实有效,但是请打开我提供的官方文档的链接,它清楚地表明我们应该使用 InvokeAsync。我也有asked the same question in github
    • OnLocationChanged 是 Blazor 引发的事件,因此您无需使用 InvokeAsync 来获取同步上下文。我引用的是您链接到的部分上方的 2 段。这就是为什么没有它也可以工作的原因。
    【解决方案2】:

    使用异步标记方法不会更改方法的签名,因此您应该这样做:

    private async void OnLocationChanged(object sender, LocationChangedEventArgs args)
    {
       // sync action:
       SyncValue = (Counter * 1000).ToString();
    
       await DoSomeAsync();
       StateHasChanged();
    }
    
    

    【讨论】:

    • 您是否尝试将计数器设为静态? private int Counter = 1; 每次页面加载后,这将始终设置为 1。
    • 关闭。您在await DoSomeAsync(); 之后缺少StateHasChanged();
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-23
    • 2017-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-21
    相关资源
    最近更新 更多