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 */));
}
}
///
/// 此元件專用來接收 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()); /// <----- 訊息銜接回主體
}
}
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