Calling async method from ViewModel - c#

I have a ViewModel, which makes use of Commanding. In this ViewModel I want to access the MediaPlugin
. With this plugin I have to call Initialize(). Because it uses async calls I have some timing problems.
This is my code:
public ICommand CameraCommand
{
get { return _cameraCommand ?? (_cameraCommand = new Command(async () => await ExecuteCameraCommand(), () => CanExecuteCameraCommand())); }
}
public bool CanExecuteCameraCommand()
{
// Check if initialized before calling properties
if (!this.initialized)
InitMedia();
if (!this.initialized || !CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
return false;
}
return true;
}
public async Task ExecuteCameraCommand()
{
// Assure that it is initialized before calling method
var file = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions {});
// ...
}
private void InitMedia()
{
CrossMedia.Current.Initialize();
this.initialized = true;
}
With this code the app crashes with
You must call Initialize() before calling any properties.
at Plugin.Media.MediaImplementation.get_IsCameraAvailable()
When I start the initialization in the constructor like with this code
public MyViewModel()
{
InitData();
}
private async Task InitData()
{
// ...
await InitMedia();
}
private async Task InitMedia()
{
await CrossMedia.Current.Initialize();
this.initialized = true;
}
public bool CanExecuteCameraCommand()
{
// Check if initialized before calling properties
if (!this.initialized)
return false;
// ...
}
CanExecuteCameraCommand() is called before the initialization has finished. As a consequence false is returned and the button in the UI is disabled.
I'm testing this code in a Xamarin.Forms environment on a Windows 10 Mobile device (Windows 10 Universal).

You have not provide a Minimal, Complete, and Verifiable code example, and in particular you have not shown your implementation of the Command class. So I'm going to assume here that it's the Command class described in the Xamarin article you referenced.
The basic strategy for asynchronous initialization of your view model is this:
Disable any commands that depend on the result of the initialization, clear any properties (or set to appropriate default value) that depend on the result of the initialization.
Begin the asynchronous initialization.
Use some continuation mechanism (i.e. don't block the thread) to wait for the asynchronous initialization to complete.
When the initialization completes, re-enable previously disabled commands and update any relevant properties.
Based on the code you posted, it appears to me that the main thing missing is step #4. I.e. you're not doing anything to re-enable the previous disabled commands, even though you have a convenient place to do so:
private async Task InitMedia()
{
await CrossMedia.Current.Initialize();
this.initialized = true;
_cameraCommand.ChangeCanExecute();
}
The Command.ChangeCanExecute() method raises the CanExecuteChanged event, so that controls bound to the command can be notified when the result of the CanExecute() method will be different. By calling this method when initialization is done, this should address the problem of the button remaining disabled even after initialization has completed.
One additional note: I would not call the InitMedia() method, or do any initialization at all, from the CanExecuteCameraCommand() method. Because initialization is asynchronous, it's not like you'll be able to successfully initialize before returning from that method anyway, and you've already got a call to the initialization from the constructor. The ICommand.CanExecute() implementation should be very simple, and strictly limited to checking the current state of things and returning the bool value required according to those results.
If the above does not address your question, please improve the question by providing a good MCVE, and explain in more precise details what is going wrong, what you've tried to fix it, and what specifically you are having trouble figuring out.

Related

COMException: Cannot change thread mode after it is set on EnsureCoreWebView2Async(null) C#

I am writing a C# application that will be loading a technical chart webpage that is generated by an outside server API. I am just trying to display a webpage that i would like to be loaded in a custom panel using WebView2 (as original WebBrowser is deprecated, and Cef has x64 problems).
However, the following code fails to execute properly and is throwing a com exception, when attempting to initialize the custom view:
class ChartView : Panel
{
#nullable enable
private string? chartViewFileAddr { get; set; }
#nullable disable
private WebView2 chartWebView { get; set; }
private async void ensureWeb2Init() => await chartWebView.EnsureCoreWebView2Async(null);
private bool isInit = false;
public ChartView(string? chartViewFileAddr) : base()
{
this.chartViewFileAddr= chartViewFileAddr ?? "";
this.BorderStyle = BorderStyle.Fixed3D;
this.Size = new System.Drawing.Size(300, 500);
this.Location = new Point(0, 320);
this.Name = "ChartWebView";
chartWebView= new WebView2();
chartWebView.Size = this.Size;
chartWebView.Location = new System.Drawing.Point(0, 0);
if (isInit == false)
{
Task.Run(() => this.ensureWeb2Init()).Wait();
isInit = true;
}
chartWebView.CoreWebView2.Navigate("google.com"); //base address for testing
this.Controls.Add(chartWebView);
}
}
When it runs, it fails on calling ensureWeb2Init. If i attempt to run the task without the Wait function, it fails to run asynchronously, if i add the wait function, the async task throws the following:
System.Runtime.InteropServices.COMException: 'Cannot change thread mode after it is set. (0x80010106 (RPC_E_CHANGED_MODE))'
For whatever reason, i cannot get the WebView2 to initialize properly. Is there anything i am doing wrong here? thank you for your help.
The problem: As mentioned in the comments and the other answer, the issue is calling the method in a thread other than the UI thread. According to documentations:
Note that even though this method is asynchronous and returns a Task, it still must be called on the UI thread like most public
functionality of most UI controls.
InvalidOperationException Thrown if this instance of CoreWebView2 is already disposed, or if the calling thread isn't the thread which
created this object (usually the UI thread).
Solutions: You may want to consider either of the following solutions based on your requirements:
Set the Source property, instead of calling EnsureCoreWebView2Async
You can set the WebView2.Source property, which will initialize the CoreWebView2 implicitly if it's not already initialized. Then you don't need to call EnsureCoreWebView2Async. For example:
webView21.Source = new Uri("https://google.com");
Expose an async Initialize method, and await call it when you need
If you need to call the EnsureCoreWebView2Async, you can expose an async method which does the initializations and await call it when you need it. For example:
Define the following method for your control:
public async Task InitializeControl(string url)
{
await webView21.EnsureCoreWebView2Async();
webView21.CoreWebView2.Navigate("https://google.com");
}
Then use it like this, when you want to initialize it:
private async void Form1_Load(object sender, EventArgs e)
{
await myControl.InitializeControl("https://www.google.com");
}
Call EnsureCoreWebView2Async without await, then do rest of initializations in the event handler ofCoreWebView2InitializationCompleted event
You can call the EnsureCoreWebView2Async without await, then you can just do the initialization in the CoreWebView2InitializationCompleted event handler. For example:
webView21.CoreWebView2InitializationCompleted += (obj, args) =>
{
webView21.CoreWebView2.Navigate("https://google.com");
};
webView21.EnsureCoreWebView2Async();
More information and references:
WebView2: A good overview of initialization of the control.
Source: An overview of the Source property.
EnsureCoreWebView2Async: Considerations when calling EnsureCoreWebView2Async method.
The exception means that the code must be run on a UI thread (specifically, the UI thread that created the control). This is common for UI COM components.
The code you posted is using Task.Run to execute the code on a thread pool thread. To run it on the UI thread instead, remove the Task.Run.

Form showing only after async tasks finished

I've got this custom Task code:
public static async Task Run(this CustomForm parent, Action action)
{
parent.Enabled = false;
using (Caricamento form = new Caricamento())
{
form.TopLevel = true;
form.TopMost = true;
form.Show();
await Task.Run(action);
}
parent.Enabled = true;
}
The gif animation and the text inside the form just won't properly load until the async task finished .
ListMessaggi listForm = new ListMessaggi(ListMessaggi.Tipo.Entrata);
listForm.FormClosing += (o, args) =>
{
if (this.Controls.Count == 2)
{
args.Cancel = true;
}
};
listForm.FormBorderStyle = FormBorderStyle.None;
listForm.Dock = DockStyle.Fill;
listForm.TopLevel = false;
panel.Controls.Add(listForm);
listForm.Show();
And then, in the form which shows up upon listForm.Show() method call I've got:
Finally, this the result showing while the async task is running:
How can I improve the code to make things work properly?
Based on the additional information you provided in the comments, I think that you should try to convert your code to entirely use async / await. This will involve converting all your ADO.NET functions to the new async methods added to ADO in .NET 4.5. This will should eliminate all the Task.Run calls, as well as the messy InvokeRequired and BeginInvoke calls that you are doing to marshal control back to the UI thread.
I think you will find that, if properly implemented, you won't even need the special Run extension method; you will be able to have all your code in-line, just as in "traditional" .net development.
For example, you could use code like this to responsively load data, without locking the UI, in a form's Load event.
public async void Form1_Load(object sender, EventArgs e)
{
var data = await _dataProvider.GetSomeDataFromTheDatabase(aTotallyMadeUpVariable);
this.MyDataGrid.DataSource = data;
}
The exact same pattern works for the event handlers on comboboxes and buttons.
As a side note, event handlers in WinForms is practically the only place that async void methods are legitimately valid. Everything else, including async methods called from within event handler, should be Task returning functions.
As a bit more of a "primer" on async / await, this is how it avoids blocking the UI thread in my example above.
The async modifier on a function acts as a marker to the compiler to convert the method in to a state-machine. The compile will segment the code in the method, breaking it up at each await call, in to separate states. When the function is called, the first state is run (up to where the await is), and then the function returns to the caller. When the function that is being awaitted returns, the code after it (in the next state) is invoked as a continuation. Any data that is shared between the states, such as local variables, is moved off to an object that is passed in to each state's continuation.

How to wait on a Task to complete without deadlock in a WinForm where developer can't add async to the current method

We have a WinForm application that has a base form that all other forms inherit from.
On the base form there is a Delete button and the Delete button calls a virtual bool method called DeleteData.
public virtual void DeleteButton_Click(object sender, EventArgs e)
{
if (DeleteData())
{
// run some cleanup code
DoSomeCleanup();
}
}
public virtual bool DeleteData()
{
throw new NotImplementedException("Not implemented.");
}
Child form has overridden the DeleteData method
public override bool DeleteData()
{
try
{
// Delete some data
// Call async method that does some UI stuff and wait on it to finish
SomeSharedAsyncMethod();
return true;
}
catch(Exception ex)
{
// Handle exception
return false;
}
}
Here is the catch, SomeSharedAsyncMethod is mark as async and inside of it, it does some UI stuff like binding to textboxes and this method is called by others so the method must stay marked as "async"
public async Task SomeSharedAsyncMethod()
{
// Some code that has await and also some code that updates textboxes or other UI controls.
await Task.Delay(2000);
SomeTextBox.Text = "Some Text";
}
I can't add "async" to the "DeleteData" method because the base form is looking at the DeleteData method to run "DoSomeCleanup" and "DoSomeCleanup" would get called before DeleteData is finished.
Let's also assume that I can't add "async" to the delete button because I don't have control of that project.
I also don't want to override the DeleteButton_Click because I don't want to copy all the code that is located inside the base form DeleteButton_Click.
Here are some things I have tried:
public override bool DeleteData()
{
// Delete some data
// Call async method that does some UI stuff and wait on it to finish
// Causes a deadlock
SomeSharedAsyncMethod().Wait();
// RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
SomeSharedAsyncMethod().RunSynchronously();
// This will not wait on SomeSharedAsyncMethod to execute
SomeSharedAsyncMethod().ConfigureAwait(false);
// Cross-thread operation not valid
Task.Run(async () => { await SomeSharedAsyncMethod(); }).Wait();
// This will not wait on SomeSharedAsyncMethod to execute
Task.Run(() => { SomeSharedAsyncMethod(); }).Wait();
// This will not wait on SomeSharedAsyncMethod to execute
Task.Run(async () => { await SomeSharedAsyncMethod().ConfigureAwait(false); }).Wait();
// This will not wait on SomeSharedAsyncMethod to execute
Task.Factory.StartNew(() =>
{
SomeSharedAsyncMethod().ConfigureAwait(false);
}, Task.Factory.CancellationToken, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()).Wait();
return true;
}
We are looking for a way of getting the DeleteData method to run all its code and not return until all lines of code has completed, this includes the SomeSharedAsyncMethod.
I don't thinks there is a way to wait for SomeSharedAsyncMethod inside the DeleteData.
The DeleteData method occupies the UI thread. It needes SomeSharedAsyncMethod to be completed to release the UI thread, but SomeSharedAsyncMethod starts after DeleteData and it needs the UI thread too. In other words, you need to run something on the UI thread to be able to release it. This is the classic deadlock example. And this is exacly why you shouldn't mix the async and .Wait() in the first place.
So, how this can be fixed? I would suggest to remove all UI-related code from SomeSharedMethodAsync and add .ConfigureAwait(false) to all async method invocations inside it.
public override bool DeleteData()
{
SomeSharedMethodAsync().ConfigureAwait(false).Wait();
UpdateUI();
}
async Task SomeSharedAsyncMethod()
{
// do all async stuff here, but DON'T update the UI
await Task.Delay(2000).ConfigureAwait(false);
}
void UpdateUI()
{
// update the UI here
SomeTextBox.Text = "Some Text";
}
Of course, this should be done only as a last resort. Making all methods starting with DeleteButton_Click asynchronous is much much preferred.
There were two options I tested and both worked.
How would I run an async Task<T> method synchronously?
This was sent my oopsdazie in comment above.
check this out: stackoverflow.com/a/5097066/5779825. Hope it helps – oopsdazie Oct 10 at 20:35
Which was a repost from here
https://social.msdn.microsoft.com/Forums/en-US/163ef755-ff7b-4ea5-b226-bbe8ef5f4796/is-there-a-pattern-for-calling-an-async-method-synchronously?forum=async
Basically it is an async helper class that works against its own SynchronizationContext.
Use Stehpen Cleary -- Nito AsyncEx NuGet
https://github.com/StephenCleary/AsyncEx
Nito.AsyncEx.AsyncContext.Run(async () => await SomeSharedAsyncMethod());
I went with option 2 since it has a lot of other handy async helpers

How to postpone execution until some event happens?

I have a WebBrowser control and it has InvokeScript method, which you should call only after WebBrowser is loaded.
So I've tried something like this:
private readonly ManualResetEventSlim browserLoaded = new ManualResetEventSlim(false);
private void BrowserLoaded(object sender, NavigationEventArgs navigationEventArgs)
{
browserLoaded.Set();
}
private async Task<object> InvokeScript(string invoke, object[] parameters = null)
{
return await Task.Factory
.StartNew(() =>
{
if (!browserLoaded.Wait(TimeSpan.FromSeconds(10)))
{
throw new Exception("Timeout for waiting browser to load.");
}
})
.ContinueWith(task => parameters == null
? browser.InvokeScript(invoke)
: browser.InvokeScript(invoke, parameters), TaskScheduler.FromCurrentSynchronizationContext());
}
It does not look very nice to me, but works ok when called asynchronously.
Problem appears, when I try to read result value synchronously - app just hangs:
private string GetEnteredText()
{
return (string)InvokeScript("getEnteredText").Result;
}
I know, that I should go all the way async, but I'm wondering what to do with properties:
public override string UserText
{
get
{
return GetEnteredText();
}
set
{
SetEnteredText(value);
}
}
Or async is wrong way to go in this case at all?
Update
Property is a 'glue' between input field value in browser's page and view model in WPF, so I don't see a good way to make it as separate methods, especially because it is a part of the bigger framework (notice override keyword on it).
Once browser control is loaded, execute logic should not take long, I guess less than 10 milliseconds, that is why I would be ok with sync execution in this case. And usually browser control loads fast enough, the only reason here to delay is to make sure InvokeScript is not called before load, not because it taking long time or smth.
app just hangs
We'll, you said it yourself. You know why that happens. You're blocking on the call using Task.Result, that is what causes the deadlock.
Or async is wrong way to go in this case at all?
We don't have async properties. The reason we don't have them are because properties aren't asynchronous by nature. Stephan Cleary describes it nicely:
If your “property” needs to be asynchronously evaluated every time
it’s accessed, then you’re really talking about an asynchronous
operation.The best solution is to change the property to an async
method. Semantically, it shouldn’t be a property.
Instead of a property, make it an asynchronous method which you can await properly.
In regards to using a ManualResetEvent, I would use a TaskCompletionSource<bool> instead. It works "nicer" with TPL:
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
private void BrowserLoaded(object sender, NavigationEventArgs navigationEventArgs)
{
tcs.TrySetResult(true);
}
private async Task<object> InvokeScript(string invoke, object[] parameters = null)
{
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(10));
if (timeoutTask == await Task.WhenAny(tcs.Task, timeoutTask))
{
// You've timed out;
}
return Task.Run(() =>
{
parameters == null ? browser.InvokeScript(invoke)
: browser.InvokeScript(invoke, parameters)
});
}
I also see that you used TaskScheduler.FromCurrentSynchronizationContext() in your continuation. If you need this to execute on the UI thread, there is no point in using a threadpool thread at all.
#Noseratio adds that WebBrowser is an STA object. His answer here might help.

Asynchronous toggle button using a semaphore to avoid service load

I have a Windows Phone client with a skinned toggle button, which is acting as a "favourite" button. The checked property is then two-way bound to the ViewModel (standard MVVM pattern).
<ToggleButton IsChecked="{Binding DataContext.IsFavouriteUser, ElementName=PageRoot, Mode=TwoWay}">
When the bound boolean is changed, I want to initiate an asynchronous network call to the service.
public bool IsFavouriteUser
{
get { return _isFavouriteUser; }
set
{
if (SetProperty(ref _isFavouriteUser, value))
{
// Dispatches the state change to a RESTful service call
// in a background thread.
SetFavouriteState();
}
}
}
If the user presses the button multiple times, then many Add / Remove asynchronous service calls could be made - Hypothetically these take 2 seconds to do the network round-trip and service processing.
In the past I have used something like:
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
// I would probably dispatch this call to a background thread in the real client
public async Task<bool> SetFavouriteState()
{
try
{
await _semaphore.WaitAsync();
bool result;
if (IsFavouriteUser)
{
result = await ServiceClient.AddAsync(x);
}
else
{
result = await ServiceClient.RemoveAsync(x);
}
return result;
}
catch
{
// I wouldn't use an empty catch in production code
return false;
}
finally
{
_semaphore.Release();
}
}
However this could endlessly queue up user input; whereas the service is only interested in the latest user event - on or off - and the UI should remain responsive to user input.
What is the best way to ensure that the client doesn't send "Add/Remove/Add/Remove" if the user repeatedly hits the button. i.e. I want to ignore the middle two events and only send "Add, wait for response to complete, Remove".
Is there a better way to bind to this boolean property in an asynchronous way?
What is the best way to lock my model so that only one request in this context is ongoing at any point?
What is the best way to inform the user that something is happening while we wait for the call to happen (and maybe fail)?
There are several good patterns to deal with async re-entrancy, i.e. what happens if a user action invokes an async method while it's already in-flight. I wrote an article with several patterns here:
http://blogs.msdn.com/b/lucian/archive/2014/03/03/async-re-entrancy-and-the-patterns-to-deal-with-it.aspx
I think your problem is a special case of pattern 5 (code below).
However, please note an oddity in your specification. It's possible that the user clicks quickly enough that you get the sequence Add followed by Add (e.g. if the intervening Remove didn't get a chance to even start executing before the second click to Add arrived). So please protect against this in your own scenario-specific way.
async Task Button1Click()
{
// Assume we're being called on UI thread... if not, the two assignments must be made atomic.
// Note: we factor out "FooHelperAsync" to avoid an await between the two assignments.
// without an intervening await.
if (FooAsyncCancellation != null ) FooAsyncCancellation.Cancel();
FooAsyncCancellation = new CancellationTokenSource ();
FooAsyncTask = FooHelperAsync(FooAsyncCancellation.Token);
await FooAsyncTask;
}
Task FooAsyncTask;
CancellationTokenSource FooAsyncCancellation;
async Task FooHelperAsync( CancellationToken cancel)
{
try { if (FooAsyncTask != null ) await FooAsyncTask; }
catch ( OperationCanceledException ) { }
cancel.ThrowIfCancellationRequested();
await FooAsync(cancel);
}
async Task FooAsync( CancellationToken cancel)
{
...
}
I would suggest disabling the ToggleButton button and showing indeterminate ProgressBar when the request is fired and hiding the ProgressBar and enabling the ToggleButton when it finishes.

Categories

Resources