1

I am trying to implement a common method to handle all API call asynchronously and handle the exception. How can I get the exception inside the Task.Run() to be passed on to the main thread, so that I can handle it globally in unhandled exceptions.

Below is my global handler,

AppDomain.CurrentDomain.UnhandledException += LogUnhandledCurrentDomainException;
Dispatcher.UnhandledException += OnDispatcherUnhandledException;

Below is my Async code to call the API using Task.Run()

public async Task<T> InvokeApiAsync<T>(Func<T> function)
    {
        return await Task.Run(function);
    }

Can anyone suggest how I can pass the exception from inside Task.Run() to the main thread.

I am calling the Async function from my WPF View model, using the below method,

private async Task LoadDocuments()
    {
        IsBusy = true;
        Documents = await _serviceInvoker.InvokeApiAsync(() => _blobService.GetBlobs().ToList());
        IsBusy = false;
    }

The above method is called from Caliburn.Micro event,

 protected override async Task OnInitializeAsync(CancellationToken cancellationToken)
    {
        await LoadDocuments();            
        await base.OnInitializeAsync(cancellationToken);
    }
Vinay
  • 259
  • 4
  • 19
  • 1
    It already get's passed and surfaces after `await`. In this case that would be in `await InvokeApiAsync(....)`, wherever that is. – Panagiotis Kanavos Jun 26 '23 at 16:41
  • Please post the relevant code. How is `InvokeApiAsync` called? What does `function` contain, and what's the purpose of `InvokeApiAsync` if it's just a wrapper over `Task.Run`? If by API you mean an HTTP API, HttpClient calls are already asynchronous, they don't need `Task.Run`. Unless you `await` those calls, any errors they raise will be lost – Panagiotis Kanavos Jun 26 '23 at 16:43
  • Yes InvokeApiAsync is a wrapper over Task.Run(), So that i can handle exceptions in one location. – Vinay Jun 26 '23 at 16:51
  • It doesn't do that. It rethrows the original exception losing the call stack. Check the duplicate. There may not be any "main" thread in the first place, eg in web applications. Unobserved task exceptions aren't handled by UI threads – Panagiotis Kanavos Jun 26 '23 at 16:52
  • 1
    Is `_blobService` a wrapper over a cloud client? All cloud SDKs are async-only. It looks like your code blocks on the async operation then uses `Task.Run` to unfreeze the main thread. It would better to use the async SDK properly and `await` it in the UI. This would avoid starting a thread just to wait for a response *and* would bring back any exceptions to the very command or event handler that triggered it – Panagiotis Kanavos Jun 26 '23 at 16:56
  • How are you calling the `LoadDocuments`? It it possible that you are calling it in a [fire-and-forget](https://stackoverflow.com/questions/60778423/fire-and-forget-using-task-run-or-just-calling-an-async-method-without-await/60781121#60781121) fashion? – Theodor Zoulias Jun 26 '23 at 21:42
  • 1
    `How can I get the exception inside the Task.Run() to be passed on to the main thread, so that I can handle it globally in unhandled exceptions.` If you always use `await`, then this already happens, by design. – Stephen Cleary Jun 27 '23 at 01:44
  • Load document is invoked through a wpf event using caliburn micro. I have updated the ticket with the event from which loadDocument is called. – Vinay Jun 27 '23 at 05:59
  • So you say that if you add in the `LoadDocuments` the line `throw new Exception();`, this exception is not surfaced? The Caliburn.Micro suppresses the exception? – Theodor Zoulias Jun 27 '23 at 08:19
  • Caliburn.Micro already handles unhandled exceptions [in the Bootstrapper](https://github.com/Caliburn-Micro/Caliburn.Micro/blob/145c4804a1235130190eaab089963dc9c6762c7f/src/Caliburn.Micro.Platform/Platforms/net46/Bootstrapper.cs#L121) by calling the virtual `OnUnhandledException`. Unlike global handlers *that* method has access to all Windows and can display its own warnings. If the application want's to handle these exceptions, that's where it needs to do it. The `InvokeAsync` method adds nothing but overhead. The calls are already on the UI thread. – Panagiotis Kanavos Jun 27 '23 at 09:41
  • In any case depending on a global handler for normal operations, like a delay retrieving cloud blobs, is a bad idea. Delays are guaranteed and *can* be retried. The correct behavior in that case is to display a delay warning or splash screen while the data is getting retrieved. Caliburn.Micro makes this easy to do, eg by replacing a `Loading` ViewModel` with a `Warning` one. Or setting a `Warning` ViewModel instance to a property that was null before – Panagiotis Kanavos Jun 27 '23 at 09:46

1 Answers1

1

Instead of Func<T> use Func<Task<T>>. Then await it directly

public async Task<T> InvokeApiAsync<T>(Func<Task<T>> function)
{
    try
    {
        return await function();
    }
    catch (Exception ex)
    {
        // whatever
        throw;
    }
}
Charlieface
  • 52,284
  • 6
  • 19
  • 43