Xamarin.Forms - BeginInvokeOnMainThread for an async Action - c#

I am familiar with the rules about updating UI elements on the UI thread using the Device.BeginInvokeOnMainThread, however I have an operation that needs to be run on the UI thread that is actually a Task.
For example, the Push/PopAsync methods on XLabs.Forms.Mvvm seem to behave incorrectly on iOS unless they are invoked on the UI thread. There is also another example in the Acr.UserDialogs library for displaying toasts etc.
I know that making an Action async is basically creating an async void lambda and runs the risk of creating a deadlock in the case of an exception, obviously I don't want this to happen.
Does anybody have a workaround for performing async operations on the UI thread that doesn't involve marking the Action as async?

Since Xamarin.Forms 4.2 there is now a helper method to run tasks on the main thread and await them.
await Device.InvokeOnMainThreadAsync(SomeAsyncMethod);
Several overloads exist that should cover most scenarios:
System.Threading.Tasks.Task InvokeOnMainThreadAsync (System.Action action);
System.Threading.Tasks.Task InvokeOnMainThreadAsync (System.Func<System.Threading.Tasks.Task> funcTask);
System.Threading.Tasks.Task<T> InvokeOnMainThreadAsync<T> (System.Func<System.Threading.Tasks.Task<T>> funcTask);
System.Threading.Tasks.Task<T> InvokeOnMainThreadAsync<T> (System.Func<T> func);
Related PR: https://github.com/xamarin/Xamarin.Forms/pull/5028
NOTE: the PR had some bug that was fixed in v4.2, so don't use this in v4.1.

Just make sure you handle exceptions in your Action and you should be fine. The problem you described occurs when you don't handle the exceptions. Below is a very simple example of running an async method from the main thread.
private void Test()
{
Device.BeginInvokeOnMainThread(SomeMethod);
}
private async void SomeMethod()
{
try
{
await SomeAsyncMethod();
}
catch (Exception e) // handle whatever exceptions you expect
{
//Handle exceptions
}
}
private async Task SomeAsyncMethod()
{
await Navigation.PushModalAsync(new ContentPage());
}

Dropping this here in case someone wants to await the end of an action which has to be executed on the main thread
public static class DeviceHelper
{
public static Task RunOnMainThreadAsync(Action action)
{
var tcs = new TaskCompletionSource<object>();
Device.BeginInvokeOnMainThread(
() =>
{
try
{
action();
tcs.SetResult(null);
}
catch (Exception e)
{
tcs.SetException(e);
}
});
return tcs.Task;
}
public static Task RunOnMainThreadAsync(Task action)
{
var tcs = new TaskCompletionSource<object>();
Device.BeginInvokeOnMainThread(
async () =>
{
try
{
await action;
tcs.SetResult(null);
}
catch (Exception e)
{
tcs.SetException(e);
}
});
return tcs.Task;
}
}

Evaluating the Maui framework and the related WeatherTwentyOne demo. The dialogs were throwing exceptions on Android 29 related to auth modifications. Hat tip to this post Why does my app crash after alert dialog buttons are clicked
Anyway, I could not locate the aforementioned async helpers. So I adapted #Dbl's post to include awaitable generic results (or not)
using System;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
// ReSharper disable AsyncVoidLambda
namespace WeatherTwentyOne.Utils
{
public static class DeviceHelper
{
// https://stackoverflow.com/a/47941859/241296
public static Task<T> RunOnMainThreadAsync<T>(Func<Task<T>> op)
{
var tcs = new TaskCompletionSource<T>();
Device.BeginInvokeOnMainThread(async
() => {
try
{
var t = await op();
tcs.SetResult(t);
}
catch (Exception e)
{
tcs.SetException(e);
}
});
return tcs.Task;
}
public static Task RunOnMainThreadAsync(Func<Task> op)
{
var tcs = new TaskCompletionSource();
Device.BeginInvokeOnMainThread(async
() => {
try
{
await op();
tcs.SetResult();
}
catch (Exception e)
{
tcs.SetException(e);
}
});
return tcs.Task;
}
}
}
Example usage:
var password = await Utils.DeviceHelper.RunOnMainThreadAsync<string>(async () => await DisplayPromptAsync("Login", "Enter password"));
Edit: 2022-03-26
In response to the following warning:
   BeginInvokeOnMainThread(Action) is obsolete: Use BindableObject.Dispatcher.Dispatch() instead
implementation revised to:
public static class DeviceHelper
{
public static Task<T> RunOnMainThreadAsync<T>(Func<Task<T>> op)
{
var tcs = new TaskCompletionSource<T>();
Application.Current?.Dispatcher.Dispatch(async () =>
{
try
{
var t = await op();
tcs.SetResult(t);
}
catch (Exception e)
{
tcs.SetException(e);
}
});
return tcs.Task;
}
public static Task RunOnMainThreadAsync(Func<Task> op)
{
var tcs = new TaskCompletionSource();
Application.Current?.Dispatcher.Dispatch(async () =>
{
try
{
await op();
tcs.SetResult();
}
catch (Exception e)
{
tcs.SetException(e);
}
});
return tcs.Task;
}
}

Related

TaskCanceledException: Cancel or timeout?

I only want to use CancellationTokenSource for timeout and cancellation handling.
How to distinguish if a TaskCanceledException occured due to a timeout or due to manual cancellation?
Here is a simplified example. In the real program I neither know if CancellationTokenSource .CancelAfter() was used nor if someone called CancellationTokenSource.Cancel()
static CancellationTokenSource cts = new CancellationTokenSource();
static void Main(string[] args)
{
Task.Run(async () =>
{
try
{
await SomeClass.DoSomething(cts.Token);
}
catch (TaskCanceledException ex)
{
//How to find out if the exception occured due to timeout or a call to cts.Cancel()
}
});
while (true)
{
Thread.Sleep(100);
if (someCondition)
cts.Cancel();
}
}
public class SomeClass
{
public static async Task DoSomething(CancellationToken ct)
{
using (var innerCts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
innerCts.CancelAfter(1000);
//Simulate some operation
await Task.Delay(10000, innerCts.Token);
}
}
}
thanks
Tom
AFAIK this is the most commonly used pattern:
Task.Run(async () =>
{
try
{
await SomeClass.DoSomething(cts.Token);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
// cts cancellation occurred
}
catch (OperationCanceledException)
{
// Timeout occurred
}
});
Another idea is to change the implementation of the SomeClass.DoSomething method, assuming that you are allowed to do it, so that in case of timeout it throws a TimeoutException instead of an OperationCanceledException.
public static async Task DoSomething(CancellationToken cancellationToken)
{
using var innerCts = new CancellationTokenSource(millisecondsDelay: 1000);
using var linkedCts = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken, innerCts.Token);
try
{
// Simulate some operation
await Task.Delay(10000, linkedCts.Token);
}
catch (OperationCanceledException) when (innerCts.IsCancellationRequested)
{
throw new TimeoutException();
}
}

Awaiting manually created task is freezing ASP.NET app

I've made a queue, which contains tasks to do. After creating some tasks manually with new Task() in Returns method, my whole application hangs - await current;. The body of the task is not even triggered.
ConfigureAwait(false) didn't help.
The first task in the queue, which is not created by me, but other framework is executing successfully and returning a value. Mine - doesn't. I've tried add Task.CompletedTask and then it has worked. I don't understand why I can't even reach the body of the task containing _output assignment.
IDE debugger code screenshot
---UPDATE---
The code works when I use code below. With await it doesn't. Any ideas?
current.Start();
current.Wait();
Original code
private readonly Queue<Task> _pipe;
public IPipeBuilder<TOutput> Returns(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
_pipe.Enqueue(new Task(() => // this task causes a problem and breakpoint isn't hit inside
{
_output = outputBuilder(_results);
}));
return this;
}
public async Task<TOutput> Execute()
{
Task current;
while (_pipe.TryDequeue(out current))
{
if (current.IsCommandExecution())
{
IExecutionResult result = await (Task<IExecutionResult>)current; // this awaits successfully
_results.Add(result);
}
else
{
await current; // hangs here
}
}
return await Task.FromResult(_output);
}
Usage
[HttpGet("eventflow/pipe/issue/add/{title}")]
public async Task<IActionResult> PipeAction(string title)
=> Ok(
await Pipe<IExecutionResult>()
.Validate(title)
.Handle<AddIssueCommand>(IssueId.New, title)
.Returns(results => results.First())
.Execute());
You should never use the Task constructor. This goes double on ASP.NET, since constructed tasks are always Delegate Tasks, which interfere with the ASP.NET usage of the thread pool. The actual reason that the await hangs is because manually-created tasks need to be started.
If you have synchronous work that you need to wrap into a Task to work alongside asynchronous tasks, then you should use Task.CompletedTask and Task.FromException:
private static Task SynchronousWork(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
try { _output = outputBuilder(_results); return Task.CompletedTask; }
catch (Exception ex) { return Task.FromException(ex); }
}
public IPipeBuilder<TOutput> Returns(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
_pipe.Enqueue(SynchronousWork(outputBuilder));
return this;
}
However, note that this executes outputBuilder immediately, which may not be desirable due to its side effects on _results and _output. If you want a delayed execution queue, then the type in the queue needs to be changed from Task to Func<Task>. Then you can add to it as such:
public IPipeBuilder<TOutput> Returns(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
_pipe.Enqueue(() =>
{
try { _output = outputBuilder(_results); return Task.CompletedTask; }
catch (Exception ex) { return Task.FromException(ex); }
});
return this;
}
and you would consume it by calling each delegate one at a time and inspecting the task it returns:
public async Task<TOutput> Execute()
{
while (_pipe.TryDequeue(out var currentFunc))
{
var currentTask = currentFunc();
if (currentTask.IsCommandExecution())
{
IExecutionResult result = await (Task<IExecutionResult>)currentTask;
_results.Add(result);
}
else
{
await currentTask;
}
}
return _output;
}
Okay, thank you. I've ended up with such class and Queue<Func<Task>> like you said.
public sealed class SyncTaskWrapper
{
private Func<Task> _action;
public SyncTaskWrapper(Action action)
=> _action = CreateFunc(action);
private static Func<Task> CreateFunc(Action action)
=> () =>
{
try
{
action();
return Task.CompletedTask;
}
catch (Exception exception)
{
return Task.FromException(exception);
}
};
public static implicit operator Func<Task>(SyncTaskWrapper #this)
=> #this._action;
}
with usage
_pipe.Enqueue(new SyncTaskWrapper(() =>
_output = outputBuilder(_results)));

Simple general exception handling in async code without boilerplate

We start using CancellationToken in out app a lot, so we have to change exception handling correspondingly:
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource(100);
await DoJob(cts.Token);
Console.WriteLine("Successfully finished");
}
private static async Task DoJob(CancellationToken ct)
{
try
{
await Task.Delay(1000, ct);
}
catch (Exception e) when(!(e is OperationCanceledException))
{
Console.WriteLine("Do cleanup in case of error.");
}
}
}
The idea behind this code is that if someone use catch(Exception e) (please do not blame me for this) and forgot to exclude CancellationToken, an error handling is executed, for example, there is a log that operation failed. But it is not true, id doesn't fail, it just has been canceled. And cancellation should be handled differently then failure.
It seems to me like a big boilerplate to write practically in every general catch
catch (Exception e) when(!(e is OperationCanceledException))
Is there some more robust solution with less boilerplate?
You could create a method that accepts a Func<Task> and catches the exception(s), e.g.:
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource(100);
await GeneralDoJobAndCatchException(() => DoJob(cts.Token));
Console.WriteLine("Successfully finished");
}
private static async Task GeneralDoJobAndCatchException(Func<Task> func)
{
try
{
await func();
}
catch (OperationCanceledException) { }
catch (Exception e)
{
Console.WriteLine("Do error handling");
}
}
private static async Task DoJob(CancellationToken ct)
{
await Task.Delay(1000, ct);
}
}
We're having the exact same problem. Mainly there is a while-loop that checks for the CancellationToken but you've to catch this exception.
We created the following extension method:
public static async Task<TaskStatus> HideCancellationException(this Task task)
{
try
{
await task;
return task.Status;
}
catch (OperationCanceledException)
{
return TaskStatus.Canceled;
}
}
Having this extension method allows to change this code:
while (!cancellationToken.IsCancellationRequested)
{
// do stuff here...
try
{
await Task.Delay(..., cancellationToken);
}
catch (OperationCanceledException)
{
// expected
}
}
to something like that:
while (!cancellationToken.IsCancellationRequested)
{
// Do stuff here.
await Task.Delay(..., cancellationToken).HideCancellationException();
}
Keep in mind that there is explicitly no overload for Task<T> because the return value in case of cancellation is default. You can't distinguish between default as normal task result and default as result of cancellation. In that case it's better to catch the exception.
You could get rid of the try-catch block altogether by awaiting indirectly with Task.WhenAny, and then querying the status of the completed task:
private static async Task DoJob(CancellationToken ct)
{
var completedTask = await Task.WhenAny(Task.Delay(1000, ct));
if (completedTask.IsFaulted)
{
Console.WriteLine("Error: " + completedTask.Exception.InnerException);
}
else if (completedTask.IsCanceled)
{
// Do nothing
}
else // Success
{
// Do nothing
}
}

Running a long-running Task within a Windows Service

I have written a Windows Service project which hosts a long-running message pump task which is meant to run for the duration of the service. When the service starts, it starts the task. When the service stops, it stops the task:
void OnStart()
{
MessagePump.Start();
}
void OnStop()
{
MessagePump.Stop();
}
Where MessagePump.Start does a Task.Factory.StartNew, and MessagePump.Stop signals the task to stop and does a Wait().
So far so good, but I'm wondering how best to handle faults. If the task has an unhandled exception, I'd want the service to stop but since nothing is typically Wait-ing on the task, I imagine it'll just sit doing nothing. How can I elegantly handle this situation?
UPDATE:
The consensus seems to be using 'await' or ContinueWith. Here is how I'm currently coding my Start method to use this:
public async static void Start()
{
this.state = MessagePumpState.Running;
this.task = Task.Factory.StartNew(() => this.ProcessLoop(), TaskCreationOptions.LongRunning);
try
{
await this.task;
}
catch
{
this.state = MessagePumpState.Faulted;
throw;
}
}
Make you MessagePump.Start() method return the task. Then
MessagePump.Start().ContinueWith(t =>
{
// handle exception
},
TaskContinuationOptions.OnlyOnFaulted);
UPDATE:
I would do the next:
private MessagePump _messagePump;
async void OnStart()
{
this._messagePump = new MessagePump();
try
{
// make Start method return the task to be able to handle bubbling exception here
await _messagePump.Start();
}
catch (Exception ex)
{
// log exception
// abort service
}
}
void OnStop()
{
_messagePump.Stop();
}
public enum MessagePumpState
{
Running,
Faulted
}
public class MessagePump
{
private CancellationTokenSource _cancallationTokenSrc;
private MessagePumpState _state;
public async Task Start()
{
if (_cancallationTokenSrc != null)
{
throw new InvalidOperationException("Task is already running!");
}
this._state = MessagePumpState.Running;
_cancallationTokenSrc = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => this.ProcessLoop(_cancallationTokenSrc.Token), _cancallationTokenSrc.Token);
try
{
await task;
}
catch
{
this._state = MessagePumpState.Faulted;
throw;
}
}
public void Stop()
{
if (_cancallationTokenSrc != null)
{
_cancallationTokenSrc.Cancel();
_cancallationTokenSrc = null;
}
}
public void ProcessLoop(CancellationToken token)
{
// check if task has been canceled
while (!token.IsCancellationRequested)
{
Console.WriteLine(DateTime.Now);
Thread.Sleep(1000);
}
}
}
You can try something like this :
void OnStart()
{
MessagePump.StartAsync();
MessagePump.ErrorEvent += OnError();
}
Then your StartAsync will look something like:
public async Task StartAsync()
{
// your process
// if error, send event to messagePump
}
And if you decide to use Tasks, then it is better to use Task.Run and not Task.Factory.StartNew()

Try-Catch Async Exceptions

This example "fails":
static async void Main(string[] args)
{
try
{
await TaskEx.Run(() => { throw new Exception("failure"); });
}
catch (Exception)
{
throw new Exception("success");
}
}
That is, the exception with the text "failure" bubbles up.
Then I tried this workaround:
static async void Main(string[] args)
{
try
{
await SafeRun(() => { throw new Exception("failure"); });
}
catch (Exception)
{
throw new Exception("success");
}
}
static async Task SafeRun(Action action)
{
var ex = default(Exception);
await TaskEx.Run(() =>
{
try
{
action();
}
catch (Exception _)
{
ex = _;
}
});
if (ex != default(Exception))
throw ex;
}
That didn't help either.
I suppose my Async CTP refresh installation could be hosed.
Should this code work as I expect ("success" bubbles up, not "failure"), or is this not "supposed" to work that way. And if not, how would you work around it?
The behavior you are seeing is likely an edge case bug or may even be correct, if unintuitive. Normally when you invoke an async method synchronously, it wraps a task around to execute and since there is no one waiting on the task to finish, the exception never makes it to the main thread. If you were to call Main directly it would succeed, but then your runtime would see an exception of "success" on another thread.
Since main is the entrypoint of your application, it is invoked synchronously and likely as the entrypoint doesn't trigger the Task wrapping behavior, so that await isn't run properly and the TaskEx.Run throws on its own thread, which shows up in the runtime as an exception being thrown on another thread.
If you were to run main as an async method, i.e. returning a Task (since an async that returns void can only really be called via await) and blocking on it from your synchronous main context, you would get the appropriate behavior as the below test illustrates:
static async Task Main() {
try {
await TaskEx.Run(() => { throw new Exception("failure"); });
} catch(Exception) {
throw new Exception("success");
}
}
static async Task Main2() {
await Main();
}
[Test]
public void CallViaAwait() {
var t = Main2();
try {
t.Wait();
Assert.Fail("didn't throw");
} catch(AggregateException e) {
Assert.AreEqual("success",e.InnerException.Message);
}
}
[Test]
public void CallDirectly() {
var t = Main();
try {
t.Wait();
Assert.Fail("didn't throw");
} catch(AggregateException e) {
Assert.AreEqual("success", e.InnerException.Message);
}
}
I.e. the Task faults with an AggregateException which contains the success exception as it's inner exception.

Categories