In my code I have a task called "ShowMessageBoxAsync". I want to use this code to show (and await) the DisplayAlert to the user and return the result. Like this: var messageBoxResult = await View.ShowMessageBoxAsync("This is an error");
The code for the ShowMessageBoxAsync is:
public async System.Threading.Tasks.Task<bool> ShowMessageBoxAsync(string message)
{
var result = false;
Device.BeginInvokeOnMainThread(async () =>
{
result = await DisplayAlert("Error", message, "OK", "Cancel");
});
return result;
}
Before I added the Device.BeginInvokeOnMainThread, the task gave me an Exception that it wasn't running on the main/UI thread. So after adding BeginInvokeOnMainThread, it started to work without exceptions. The problem, however, is that the code goes directly to the result, without waiting for the result of the "await DisplayAlert".
Is it possible return the value of "result" only after the Device.BeginInvokeOnMainThread code finishes?
I did some research about it and someone suggested to use a TaskCompletionSource, but this blocks the UI thread and the DisplayAlert doesn't show up at all.
I did some research about it and someone suggested to use a TaskCompletionSource
That is the correct solution. TaskCompletionSource
acts as a "completer" for a Task
. In this case, this Task
is representing the user interaction, so you want the code on the UI thread to do the completing of the Task
, and the code on the background thread to (a)wait for the `Task.
So, something like this:
public Task<bool> ShowMessageBoxAsync(string message)
{
var tcs = new TaskCompletionSource<bool>();
Device.BeginInvokeOnMainThread(async () =>
{
try
{
var result = await DisplayAlert("Error", message, "OK", "Cancel");
tcs.TrySetResult(result);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
});
return tcs.Task;
}
This should get you unblocked for now. However, a better long-term solution would be to have your background thread logic take some kind of "interact with the UI" interface like this:
public interface IAskUser
{
Task<bool> AskUserAsync(string message);
}
with a Xamarin-Forms-specific implementation similar to above.
That way, your background thread logic isn't tied to a specific UI, and can be more easily unit tested and reused.
This question is worth an update now - in the latest version of Xamarin Forms there is now an awaitable method called Device.InvokeOnMainThreadAsync
which is easier to use and less verbose than the other answers, in my opinion.
var result = false;
await Device.InvokeOnMainThreadAsync(async () =>
{
result = await DisplayAlert("Error", message, "OK", "Cancel");
});
return result;
Or if you're not running in Xamarin forms you can reference Xamarin.Essentials from Xamarin.iOS, Xamarin.Android or Xamarin.UWP too and call the MainThread extension like this
var result = false;
await MainThread.InvokeOnMainThreadAsync(async () =>
{
result = await DisplayAlert("Error", message, "OK", "Cancel");
})
return result;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With