InvokeAsync 與 StateHasChanged 非同步刷新UI問題 (MessageBus 應用)

Blazor Comonent 的 StateHasChanged() 在 Message Bus 觸發 UI render 無效問題解決。BlazorComponentBus,MessageBus.

引言

Blazor Component UI 的刷新一般用 StateHasChanged() 觸發就行,但在 Message Bus 的應用 StateHasChanged 會無效!

在 Message Bus 的應用若要顯示訊息一般用 toast (或 snackebar) 模組,這機制都能如期運作。然有時也有呈現在主頁面的需求,這時卻會無效。研究後,Message Bus 的通訊一般是採用訂閱(subscription)機制,這方法在主頁面回饋時訊息是有如實送到的,但畫面刷新用 StateHasChanged() 觸發無效!推論是其領域(domain)不同所以訊息銜接(context)到另一個時空去了。

解決方法

ComponentBase.InvokeAsync 函式就能再銜結回主頁面。

先下結論

若 StateHasChanged 無效的話就用:

await InvokeAsync(() => StateHasChanged());

關鍵程式碼紀錄

本案例的 Message Bus 採用 BlazorComponentBus 元件實作,也是用訂閱機制通訊。

@page "/DEMO007"
@inject SampleBiz bizSvc
@inject BlazorComponentBus.ComponentBus busSvc

<PageTitle>各項機制測試</PageTitle>

@* 命令列 *@
<MudStack Row Justify=Justify.Center Class="my-4">
  <MudButton Variant=Variant.Filled OnClick=HandlePostSnack>測試送 NotifyMessage 法一</MudButton>
  <MudButton Variant=Variant.Filled OnClick=HandlePostSnack2>測試送 NotifyMessage 法二</MudButton>
</MudStack>
  
@* 接收 Message Bus 訊息 *@
<NotifyMessageResp />

@code {
  void HandlePostSnack()
  {
    // 送出一個訊息
    busSvc.Publish(new NotifyMessage { Message = "我出運了!" });
  }

  async Task HandlePostSnack2()
  {
    // 在後端商業邏輯層送出數個訊息,並將反應到前端
    await Task.Run(() => bizSvc.SimsLongtermProcedure(/* Args */));
  }
}
NotifyMessageResp.razor
///
/// 此元件專用來接收 Message Bus 訂閱訊息
/// 
@implements IDisposable
@inject BlazorComponentBus.ComponentBus busSvc
@inject ISnackbar snackSvc

@foreach (var msg in msgList)
{
  <p>@($"{msg.Message} at {msg.MsgTime:HH:mm:ss}")</p>
}

@code {
  List<NotifyMessage> msgList = new();

  void IDisposable.Dispose()
  {
    busSvc.UnSubscribe<NotifyMessage>(HanldeNotifyMessageAsync);
    //※ 記得有訂閱就要解訂閱。
  }

  protected override void OnInitialized()
  {
    base.OnInitialized();
    // 訂閱訊息
    busSvc.Subscribe<NotifyMessage>(HanldeNotifyMessageAsync);
  }

  async Task HanldeNotifyMessageAsync(BlazorComponentBus.MessageArgs args, CancellationToken ct)
  {
    // 取出訊息
    var msg = args.GetMessage<NotifyMessage>();
    
    // 以 toast/snackbar 呈現訊息
    snackSvc.Add($"{msg.Message} at {msg.MsgTime:HH:mm:ss}");

    // 或呈現(反應)到主頁面
    msgList.Add(msg);
    await InvokeAsync(() => StateHasChanged()); /// <----- 訊息銜接回主體
  }
}
SampleBiz.cs
class SampleBiz
{
  //## injection
  readonly ILogger<SampleBiz> _logger;
  readonly BlazorComponentBus.ComponentBus _busSvc;
  
  public SampleBiz(ILogger<SampleBiz> logger, IAuthUser BlazorComponentBus.ComponentBus busSvc)
  {
    _logger = logger;
    _busSvc = busSvc;
  }

  public List<DataInfo> SimsLongtermProcedure(args)
  {
    _busSvc.Publish(new NotifyMessage { Message = "某處理程序:開始" });

    // 模擬跑一段時間(3秒)
    SpinWait.SpinUntil(() => false, 3000);

    _busSvc.Publish(new NotifyMessage { Message = "某處理程序:步驟二" });

    // 模擬跑一段時間(3秒)
    SpinWait.SpinUntil(() => false, 3000);

    _busSvc.Publish(new NotifyMessage { Message = "某處理程序:步驟三" });

    // 模擬跑一段時間(3秒)
    SpinWait.SpinUntil(() => false, 3000);

    _busSvc.Publish(new NotifyMessage { Message = "某處理程序:結束" });
    return dataList;
  }
}

沒圖沒真象

參考文章

Last updated