I'm currently working on a .net 5 Blazor application.
I use events to pass data from one component to another.
Unfortunately my current logic is synchronous - but I would rather use an asynchronous event handler.
Thus, I need to use the following code to handle my event:
Task.Run(async () => await ChangeNumbers());
Is there a possibility to handle events asynchronously without Task.Run?
My State service looks like this:
public class MyComponentState
{
public int MyNumber { get; set; }
// Is there a way to declare this event Action async??
public event Action OnChange;
public void DoStuff(int myNumber)
{
MyNumber = myNumber;
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
The component to handle the state looks like this:
public class MyOtherComponentDisplay : ComponentBase, IDisposable
{
[Inject]
public MyComponentState MyComponentState { get; set; }
protected override void OnInitialized()
{
// this should all be handled async... i could use OnInitializedAsync
MyComponentState.OnChange += OnChangeHandler;
}
private void OnChangeHandler()
{
// Is there a way to handle/declare this without Task.Run(async ...) - but async right away??
Task.Run(async () => await ChangeNumbers());
}
private async Task ChangeNumbers()
{
// Some operations with MyComponentState.MyNumber async needed!!!
StateHasChanged();
}
public void Dispose()
{
MyComponentState.OnChange -= OnChangeHandler;
}
}
Is there a way to declare and handle events async?
Do you know how to solve this problem?
The basic adoptation would be an async void handler:
private async void OnChangeHandler()
{
// Is there a way to handle/declare this without Task.Run(async ...)
// - but async right away??
// Task.Run(async () => await ChangeNumbers());
await ChangeNumbers();
await InvokeAsync(StateHasChanged); // probably needed
}
The way you're doing things looks strange to me. That's not how I do events in Blazor. (Maybe you're coming from Web Forms?)
Generally, a custom event is defined like:
MyControl.razor
[Parameter]
public EventCallback<SomeType> EventName{ get; set; }
#code {
someMethod (){
EventName.InvokeAsync(SomeType data);
}
}
And the handler in the consuming control can be async if you want:
MyPage.razor
<MyControl EventName=OnChangeHandler />
#code {
private async Task OnChangeHandler()
{
await ChangeNumbers();
}
}
Related
In blazor wasm (v6), I have an app-wide state container (AppState.razor) as a cascading parameter.
The typical code is:
AppState.razor
<CascadingValue Value="this">
#ChildContent
</CascadingValue>
#code {
[Parameter] public RenderFragment ChildContent { get; set; }
private bool _isFoo;
public bool IsFoo {
get {
return _isFoo;
}
set {
_isFoo = value;
StateHasChanged();
//await SomeAsyncMethod(); // <----------
}
}
//...
}
App.razor
<AppState>
<Router>
...
</Router>
</AppState>
MyComponent.razor
<!--- markup --->
#code {
[CascadingParameter] public AppState AppState { get; set; }
//...
}
After a component sets the AppState.IsFoo property to update the state (and update the UI), I must call an async method (e.g. save to localstorage). But I cannot do that in a sync property setter.
I could change from a cascading parameter to an injectable service, but I prefer not to.
I may need to redesign - what is the typical approach for this use case? (I've seen code with InvokeAsync(SomeAsyncMethod) without an await but I'm wary of that.)
I figured out a workaround.
My requirements:
I wanted a simple state container as advocated by the docs
I didn't want the state container as a service (as shown in the docs), because I wanted to avoid the boilerplate of event handlers and disposal in all consuming components
I wanted the state container as a global cascading parameter, as it's so simple
The trick is to trigger a rerender, and then to perform async work in the OnAfterRenderAsync handler.
AppState.razor
<CascadingValue Value="this">
#ChildContent
</CascadingValue>
#code {
private bool _isDirty; // <-----
[Parameter] public RenderFragment ChildContent { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender) {
await base.OnAfterRenderAsync(firstRender);
if (firstRender) {
await LoadStateFromLocalStorage();
}
if (!firstRender && _isDirty) { // <-----
await SaveStateToLocalStorage();
_isDirty = false;
}
}
private bool _isDarkMode;
public bool IsDarkMode {
get {
return _isDarkMode;
}
set {
if (value == _isDarkMode) return;
_isDarkMode = value;
_isDirty = true; // <-----
StateHasChanged();
}
}
//other properties...
private async Task LoadStateFromLocalStorage() {
Console.WriteLine("LOADED!");
await Task.CompletedTask;
}
private async Task SaveStateToLocalStorage() {
Console.WriteLine("SAVED!");
await Task.CompletedTask;
}
}
The example async code above is for loading / saving to localstorage, but one could do any async work in the same way.
Notes
It could be that concurrency issues would cause rerenders and async invocations to occur in an unexpected order. But it should be fine for most use cases, and is good enough for me. If you want something more robust with a guaranteed order, then use flux (which is overkill for something so simple).
This is meant for very simple app-wide stuff. I wouldn't put all my state in there. For most components I'd use other cascading parameters and injectable services.
Here's a different solution that I think resolves most of the issues in your implementation. It loosely based on the way EditContext works.
First separate out the data from the component. Note the Action delegate that is raised whenever a parameter change takes place. This is basically the StateContainer in the linked MSDocs article.
public class SPAStateContext
{
private bool _darkMode;
public bool DarkMode
{
get => _darkMode;
set
{
if (value != _darkMode)
{
_darkMode = value;
this.NotifyStateChanged();
}
}
}
public Action? StateChanged;
private void NotifyStateChanged()
=> this.StateChanged?.Invoke();
}
Now the State Manager Component.
We cascade the SPAStateContext not the component itself which is far safer (and cheaper).
We register a fire and forget handler on StateChanged. This can be async as the invocation is fire and forget.
#implements IDisposable
<CascadingValue Value=this.data>
#ChildContent
</CascadingValue>
#code {
private readonly SPAStateContext data = new SPAStateContext();
[Parameter] public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
=> data.StateChanged += OnStateChanged;
private Action? StateChanged;
// This implements the async void pattern
// it should only be used in specific circumstances such as here in a fire and forget event handler
private async void OnStateChanged()
{
// Do your async work
// In your case do your state management saving
await SaveStateToLocalStorage();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await LoadStateFromLocalStorage();
}
public void Dispose()
=> data.StateChanged -= OnStateChanged;
}
I have a class / service in my Blazor MAUI application that regularly manipulates the data that is stored within it. There is an internal schedular on a fixed interval that regenerates the value.
I have a Blazor component that reads the value from this service. When the value changes in my service, I would like my Blazor component to reflect that change.
Just to keep it simple, lets take the following:
public class EmployeeService {
public int NumberOfEmployees { get; private set; }
public EmployeeService() {
// Logic to initialize a fixed scheduled for function
// RecalculateNumberOfEmployees();
}
private async void RecalculateNumberOfEmployees() {
numberOfEmployees += 1;
}
}
#path "/employees"
#inject EmployeeService Service
Number of employees: #Service.NumberOfEmployees
#code {
}
I found a recommendation here that uses a timer to invoke StateHasChanged() but I really, really don't like that approach. It seems like it is a waste of resources and an anti-pattern.
My next step is to make EmployeeService accept EventCallback from the Blazor component and store that in a list. This would allow any component to listen for changes in the EmployeeService class. When a component is unmounted, it will delete the callback.
Something like:
EmployeeService.cs
public List<EventCallback> listeners { get; private set; } = new List<EventCallback>();
public async void RegisterCallback(EventCallback callback) {
listeners.ForEach(...notify listeners);
}
public async void DeregisterCallback(EventCallback callback) {
listeners.Remove ... etc
}
Employees.razor
...
#code {
// register / deregister callback and listen for changes, invoke StateHasChanged()
}
Before I go down this route, are there any better design patterns that I could use for future components that would be better suited for this purpose? I feel like this is something that should already be baked into the framework but haven't seen anything that addresses it.
You could use an event Action:
EmployeeService.cs
public class EmployeeService
{
public int NumberOfEmployees { get; private set; }
public EmployeeService() {
// Logic to initialize a fixed scheduled for function
// RecalculateNumberOfEmployees();
}
private async void RecalculateNumberOfEmployees() {
numberOfEmployees += 1;
NotifyStateChanged();
}
public event Action OnChange;
private void NotifyStateChanged() => OnChange?.Invoke();
}
Employees.razor
#inject EmployeeService EmployeeService
#implements IDisposable
Number of employees: #EmployeeService.NumberOfEmployees
#code {
protected override void OnInitialized()
{
EmployeeService.OnChange += OnChangeHandler;
}
public void Dispose()
{
EmployeeService.OnChange -= OnChangeHandler;
}
private async void OnChangeHandler()
{
await InvokeAsync(StateHasChanged);
}
}
Another possibility is to use the Event Aggregator pattern. Take a look at this library:
https://github.com/mikoskinen/Blazor.EventAggregator
I have async/awaited methods in my Connected anonymous method implementation and I wonder which one of these is the best to use in my case. I put examples below and added information according to what I know. Are my statements correct? Anything else important that I'm missing?
EventHandler
The only disadvantage to me is that the anonymous method is basically async void. Yes, we can have async calls, but they will be kinda "fire and forget/crash".
public class Test
{
public event EventHandler<ConnectEventArgs>? Connected;
public ValueTask StartAsync()
{
Connected?.Invoke(this, new ConnectEventArgs(...))
}
}
var test = new Test();
test.Connected += async (sender, e) => // private async void OnConnect(object? sender, ConnectEventArgs e) => BAD
{
await SendAsync(); // fire and forget
};
await test.StartAsync();
Func
What I notice is that we can have async calls without having to worry about async void. I think that's the only good way to have async/awaited method calls. Isn't it?
public class Test
{
public event Func<Task>? Connected;
public ValueTask StartAsync()
{
await (Connected?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
}
}
var test = new Test();
test.Connected += async () => // private async Task OnConnect()
{
await SendAsync(); // Should be okay?
};
await test.StartAsync();
Action
The only difference between Action and EventHandler that comes into my mind is that we don't pass the sender and we don't have to create EventArgs classes.
public class Test
{
public event Action<bool>? Connected;
public ValueTask StartAsync()
{
Connected?.Invoke(true);
}
}
var test = new Test();
test.Connected += async (boolean) => // private void OnConnect(bool boolean)
{
await SendAsync(); // fire and forget
};
await test.StartAsync();
For some reason, despite this question coming up a lot in my googling, I can't seem to find an actual answer. Maybe I'm just using delegates wrong, I'm not sure. I'm happy for alternative ways of handling this if it's an X-Y problem.
Say I have this:
public class SomeLibrary
{
public delegate void OnSomethingHappened(EventInfo eventInfo);
public OnSomethingHappened onSomethingHappened;
public void SomeMethod()
{
// ...
// Something happened here, so we'd better trigger the event
onSomethingHappened?.Invoke(eventInfo);
// ...
}
}
public class MyCode
{
public void SomeInitialisationMethod()
{
SomeLibrary someLibrary = new SomeLibrary();
someLibrary.onSomethingHappened += SomeEventHandler;
}
private void SomeEventHandler(EventInfo eventInfo)
{
DoSyncProcessing(eventInfo);
}
}
That should all be fine (barring silly typos).
Now imagine my regular synchronous DoSyncProcessing function suddenly has to become asyncronous, like in this magic non-functional code:
public class SomeLibrary
{
public async delegate Task OnSomethingHappened(EventInfo eventInfo); // <<< IDK what I'm doing here!
public OnSomethingHappened onSomethingHappened;
public void SomeMethod()
{
// ...
// Something happened here, so we'd better trigger the event
await onSomethingHappened?.Invoke(eventInfo); // <<< IDK what I'm doing here either!
// ...
}
}
public class MyCode
{
public void SomeInitialisationMethod()
{
SomeLibrary someLibrary = new SomeLibrary();
someLibrary.onSomethingHappened += SomeEventHandler;
}
private async Task SomeEventHandler(EventInfo eventInfo)
{
await DoAsyncProcessing(eventInfo);
}
}
How can I handle that? What's the correct way to do this?
The async modifier affects the method implementation, not the signature. So change this:
public async delegate Task OnSomethingHappened(EventInfo eventInfo);
To this:
public delegate Task OnSomethingHappened(EventInfo eventInfo);
and your code will work.
I am lost in this one, i want my Viewmodel to use a event delegate so i can subscribe to it, open some dialog and wait for the dialog result. Later the ViewModel should do whatever it wants with the dialog result.
Here is how i implemented it (resumed code):
public class MyViewModel()
{
public delegate TributaryDocument SearchDocumentEventHandler();
public event SearchDocumentEventHandler SearchDocument;
//Command for the search button
public CommandRelay SearchDocumentCommand { get; set; }
//Document that i found in the dialog.
public TributaryDocument Document { get; set; }
public MyViewModel()
{
SearchDocumentCommand = new CommandRelay(DoSearchDocument);
}
//The command execution
public void DoSearchDocument()
{
//Event used here !
Document = SearchDocument?.Invoke();
}
}
public class MyUIControl : UserControl
{
public MainWindow MainWindow { get; }
public MyUIControl()
{
MainWindow = Application.Current.Windows[0] as MainWindow;
DataContextChanged += MyUIControl_DataContextChanged;
}
private void MyUIControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var modelView = (MyViewModel)DataContext;
modelView.SearchDocument += MyUIControl_SearchDocument;
}
private TributaryDocument MyUIControl_SearchDocument()
{
//Dont know what to do here... i am lost on this part.
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
}
//The signature for MainWindow.ShowDialog
public async Task<object> ShowDialog(object dialog)
{
return await DialogHost.Show(dialog, "MainDialog");
}
MyDocumentSearcherDialog is just a dialog where i search and return a TributaryDocument object.
The problem to my understanding comes from this part (since i cant compile it):
private TributaryDocument MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
I cant use await without changing the method signature to async. If i change it to async then i must return a Task<TributaryDocument> and change the event delegate:
public delegate Task<TributaryDocument> SearchDocumentEventHandler();
//On MyUIControl
private Task<TributaryDocument> MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
//On MyViewModel
public async void DoSearchDocument()
{
//Event used here !
Document = await Task.Run(async () => await SearchDocument?.Invoke());
}
If i do this i get the following exception:
Additional information: The calling thread must be STA, because many
UI components require this.
It seems like all you need to do is to remove the Task.Run (there is no need to Offload to another thread in this situation). The Task.Run will definitely give you a STA Thread Exception if you are doing UI work from within.
However, in short the Async and Await Pattern will create a continuation with the current SynchronisationContext, so there is no need to worry about it.
public async void DoSearchDocument()
{
await SearchDocument?.Invoke();
}
Note : Since this is an event, it's about the only place it's OK to use async void.