Use asynchonous tasks with INavigationAware with Prism - c#

How to call an async method into a method that implements an interface when this method is NOT async?
In my ViewModel, I've got an async method that should be executed each time the user navigates into the view.
The fastest (bad) solution I was able to find is this one
public class MyViewModel : INavigationAware
{
//[...]
public async void OnNavigatedTo(NavigationContext navigationContext)
{
await Refresh();
}
public async Task Refresh()
{
await Task.Run(() =>
{
// Asynchronous work
});
/* Actions on Gui Thread */
}
//[...]
}

This should work for this, based on what I can gather from the question. I did a quick test with a project I am working on to see the concept work. The Refresh method should run each time you navigate to the view using this view model.
public class MyViewModel : INavigationAware
{
//[...]
public void OnNavigatedTo(NavigationContext navigationContext)
{
Refresh();
}
public async void Refresh()
{
await Task.Run(() =>
{
// Asynchronous work
}).ContinueWith(t=> /* Actions on Gui Thread */, TaskScheduler.FromCurrentSynchronizationContext());
}
//[...]
}
I removed the async/await on the OnNavigatedTo, and had that call an async Refresh method that returns nothing.

Related

Action vs EventHandler vs Func for async/awaited method calls

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();

Why awaiting in App.OnStart() before setting the MainPage property doesn't load main page

First of all, I know the documentation states that I should set the MainPage property in the App class constructor (https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/application-class#mainpage-property).
To simplify, what I need to do is some async operation in the OnStart() event handler and after that, set the MainPage property. This question wants to get to the root issue in the framework itself.
Something like this (simplified on a blank Xamarin.Forms project, you can try if you want):
public partial class App : Application
{
public App()
{
InitializeComponent();
...
}
protected override async void OnStart()
{
//Uncomenting the following line makes the page be shown
//MainPage = new Page() { BackgroundColor = Color.Red };
//Simulate some fast async work. If you comment this line the MainPage will be shown just fine
await Task.Delay(100);
//The page is never displayed to the user unless first commented line in this method is uncommented
MainPage = new Page(){ BackgroundColor = Color.Red };
}
}
If you just don't await anything and do all code synchronous, the MainPage is set properly, but not if I await any task. Why?
As the await Task.Delay(100)call returns to the original SynchronizationContext (I assume will be on the UI thread), I don't think this is the issue.
Thanks a lot!
As you can see, OnStart returns void, not Task, so it's not intended to be asynchronous. You made it async void but caller (framework code which calls OnStart) has no means to wait for that async operation to complete. For caller, OnStart completes right when await Task.Delay hits (that's when OnStart returns), and so it continues to other stuff. When await Task.Delay completes and you set MainPage - you are not actually in OnStart stage of initialization process, that stage has already been completed 100 milliseconds ago.
Consider this example which should clarify it:
public class Program
{
public static void Main()
{
var app = new Application();
app.Start();
}
}
class Application
{
public string MainPage { get; set; }
protected virtual void OnStart()
{
}
public void Start()
{
Console.WriteLine("calling OnStart");
// can't await or anything here, it returns void
OnStart();
Console.WriteLine("OnStart called");
// now we consider initialization is done and check MainPage
if (MainPage != null)
{
Console.WriteLine(MainPage);
}
Console.WriteLine("Initialization done");
}
}
class MyApplication : Application
{
protected override async void OnStart()
{
await Task.Delay(100);
// we set MainPage but it's too late
MainPage = "Hello";
}
}

Trigger and handle Event async

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();
}
}

Call async Method from a normal method c#

I am trying to call an async method from a normal method. I have the following two issues here:
When I use Task.Run in class A to class B's method, JQuery is not
receiving the call.
I cannot use await because it throws this error:
The await can only be used in an async method
I am not sure what the issue is with respect to the first problem I am having. Secondly, is there a way to use await when calling classB because it seems to work when I made a call from a different method that is async.
public class A :IA
{
public A ()
{
consumer.Received += Received;
}
public void Received(object sender, BasicDeliverEventArgs ea)
{
//get message here and send to classB
Task.Run(() => classB.PingMesssage(Message));
}
}
public class B
{
public async Task PingMesssage(string message)
{
await InvokeClientMethodToAllAsync("callJQueryMethod", "message"); //calling js here
}
}
You can make Received async and await a call to PingMessage:
public async void Received(object sender, BasicDeliverEventArgs ea) {
await classB.PingMessage(message);
}

WPF MVVM Async event invoke

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.

Categories

Resources