Home > Enterprise >  How do you get an Exception thrown by an Task started from the UI thread (so it is not allowed to bl
How do you get an Exception thrown by an Task started from the UI thread (so it is not allowed to bl

Time:11-26

The goal is to start a Task, that loads some resources from the disk, on the UI thread, and then when it is finished to check if it threw an Exception without blocking the UI thread, and if it did, to rethrow the exception on the UI thread so the program ends and I get a stack trace.

I have been able to figure out that I can start a Task on a background thread without blocking, and awaiting the Task blocks the main thread.

I absolutely can not call await on the Task, because it would block the UI thread, but it appears I need to call it to access the Exception property after the Task has completed. I also can not use continueWith, because that also runs asynchronously, so propagating the exception from there will not work.

The docs I see all block to wait for the Exception.

https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library

https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-7.0#WaitingForOne

For the Exception property of the Task, it would be nice if I could use something like RegisterPropertyChangedCallback, but that is not an available method in the Task object.

This is the code where I want to rethrow an Exception in the UI thread without blocking it

        public TitlePage()
        {
            this.InitializeComponent();

            string baseFolder = AppDomain.CurrentDomain.BaseDirectory;
            string folder = baseFolder   @"Assets\";
            Task task = DataSource.Load(folder);
            // This is where I want to rethrow the Exception if it was thrown
            // unless I can end the program and print the stack trace
            // from the Task when it throws an Exception.
            // Note that this is inside the constructor of a Page class; 
            // it is vital that the UI thread is not blocked.
        }

CodePudding user response:

Use a "factory method": a static method that returns a new instance of the class. Then make the constructor empty and private to prevent anyone from creating an instance without using the factory method.

Something like this:

private TitlePage() {}

public static async Task<TitlePage> CreateTitlePage()
{
    var titlePage = new TitlePage();
    titlePage.InitializeComponent();

    string baseFolder = AppDomain.CurrentDomain.BaseDirectory;
    string folder = baseFolder   @"Assets\";
    Task task = DataSource.Load(folder);
    
    await task;
    return titlePage;
}

Because this static method is inside the class, you can call other private methods, including the constructor.

Then you call it like this:

var newTitlePage = await TitlePage.CreateTitlePage();

If it is called on the UI thread, any exception will be thrown on the UI thread.

CodePudding user response:

Rethrowing within the UI thread an exception that was thrown by a side-task is just a special case of the more general case of communicating to the UI thread any kind of result produced by the side-task.

(In other words, today you think you only need to throw an exception; tomorrow you are likely to discover that you also need to obtain a result.)

For this purpose I like to communicate a Result class like the one found in Scala, (and of which various implementations exist for C#,) but this is not necessary; the Task object can also be communicated after Task.IsCompleted becomes true; The UI thread can check whether it represents failure with Task.IsFaulted, and if so, it can get the exception with Task.Exception; otherwise, it can get the result with Task<T>.Result.

So, the question now becomes how to have a Task communicate a result back to the UI thread, once the task is complete.

To be able to have a task communicate a result back to the UI thread you can pass to the task a "task completion action" in the form of an Action<R> delegate, where R is the type of the result, and to implement this delegate in such a way that it posts the result into the message queue of the UI thread.

Under WPF, posting something into the UI thread is done with Dispatcher.BeginInvoke().

So, here is some code:

using Sys = System;
using Wpf = System.Windows;
using WpfThreading = System.Windows.Threading;

private void TaskCompletionAction( R result )
{
    Assert( not-in-UI-thread );
    Sys.Action action = () => ReceiveResult( result );
    Wpf.Application.Current.Dispatcher.BeginInvoke(
        WpfThreading.DispatcherPriority.Background, 
        action );
}

private void ReceiveResult( R result )
{
    Assert( in-UI-thread );
    ...do something with the result...
}
  • Related