从内部调用异步方法 NavigationManager.LocationChanged

Calling async method from inside NavigationManager.LocationChanged

我正在使用 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 内部调用异步方法时滞后?

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

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

   await DoSomeAsync();
   StateHasChanged();
}

经过大量的试验和错误,这是我的发现:

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

    填充路由参数
  2. 在异步方法结束时调用StateHasChanged()

  3. According to the documentation 我们应该用 base.InvokeAsync(() => ...)

    包装调用

经过这些修改后,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();
    });
}