2

Regarding: AsyncContextThread

https://github.com/StephenCleary/AsyncEx/wiki/AsyncContext

https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Context/AsyncContextThread.cs

It's not really covered how to handle catching exceptions that occur when the thread is started.

public partial class MyService : ServiceBase
{
    private readonly AsyncContextThread _thread = new AsyncContextThread();

    private readonly CancellationTokenSource _cts = new CancellationTokenSource();

    public MyService()
    {
        InitializeComponent();
    }

    public void Start()
    {
        OnStart(null);
    }    

    protected override void OnStart(string[] args)
    {
        try
        {     
            _thread.Factory.Run(StartAsync);
        }
        catch (Exception ex)
        {                
            // Exception?
        }
    }

    private async Task StartAsync()
    {
        throw new Exception("things went wrong");
    }

    protected override void OnStop()
    {
        if (!_cts.IsCancellationRequested)
            _cts.Cancel();
        _thread.JoinAsync().GetAwaiter().GetResult();
    }
}

I don't seem to catch anything in the catch{} block, in addition I tried adding these to my Program.cs (main entry point).

TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

However neither of the event handler methods are triggered in the above code.

Q) How do you handle exceptions correctly in this situation?

As a side note, I'm fairly sure that using AsyncContext previously and debugging in Visual Studio did catch the exception, so I'm not sure if there's some sync. context I'm missing, when trying to add a handler for exceptions.

EDIT

I know I can try/catch within the StartAsync method, but the point I'm getting at is being able to catch any unhandled Exceptions and make sure I can log them.

peteski
  • 1,455
  • 3
  • 18
  • 40
  • I'm desperately hoping for a Stephen Cleary answer to this question :) – peteski Sep 29 '17 at 14:45
  • 1
    I think you need to `await` the method in the `try` for the exception to be caught. At the moment the async method will return to the caller immediately and the error thrown is lost. – Equalsk Sep 29 '17 at 14:50
  • @Equalsk you cannot `await` anything in the `OnStart` method of a Windows Service as it's an override and must match the signature expected. The whole idea of AsyncContext/AsyncContextThread (as far as I understand) is to allow you to spin up async methods from places that wouldn't allow it (i.e. Console apps or Windows services). – peteski Sep 29 '17 at 14:53
  • Possible duplicate of [What is the best way to catch exception in Task?](https://stackoverflow.com/questions/12980712/what-is-the-best-way-to-catch-exception-in-task) – Tiago Sousa Sep 29 '17 at 14:58
  • @TiagoSousa disagree, this is specific to Stephen's AsyncContext classes. – peteski Sep 29 '17 at 15:22
  • 2
    I marked it as duplicated because the answer to the other question contains the same answer to this one: `task.ContinueWith(t => { /* error handling */ }, context, TaskContinuationOptions.OnlyOnFaulted);`, which was also the answer to this one. The fact that you are using the `AsyncContext` classes doesn't change the fact that you want to know how to handle an error on the execution of a `System.Threading.Task` (on the awaiting side) – Tiago Sousa Sep 29 '17 at 16:04

2 Answers2

3

You may use a Continuation to handle exceptions.

protected override void OnStart(string[] args)
{
    _thread.Factory.Run(StartAsync).ContinueWith(t =>
    {
        var aggregate = t.Exception.Flatten();
        foreach(var exception in aggregate.InnerExceptions)
        {
            // Handle exception
        }
    }, TaskContinuationOptions.OnlyOnFaulted);
}
Equalsk
  • 7,954
  • 2
  • 41
  • 67
  • I understand what you're saying, the reason I think I was hoping to get Stephen's input on the question is that his book "Concurrency in C# Cookbook" (page 35/36) discussed handling exceptions in async void type methods. The code example given doesn't use continuations as you've shown but rather the try/catch in my code sample, therefore this doesn't seem correct. – peteski Oct 23 '17 at 11:01
3

I've been meaning to do a blog post on asynchronous Win32 services... for a few years now... :)

It's not really covered how to handle catching exceptions that occur when the thread is started.

The AsyncContextThread is actually started immediately upon creation. What you're doing on OnStart is queueing work to its context.

The exception for that work is observed on the Task returned from Run, and the appropriate way to observe it is using await:

protected override async void OnStart(string[] args)
{
    try
    {     
        await _thread.Factory.Run(StartAsync);
    }
    catch (Exception ex)
    {                
        // Exception!
    }
}

A few notes:

Normally, you should avoid async void. The exception to that rule is event handlers or event-like methods (such as OnStart). When you do use async void, you should consider having a try/catch around the entire async void method body (which we do).

await is superior to ContinueWith. In this particular case, ContinueWith will work OK, but in the general case it's not advisable: it does not understand async delegates, it does not use an appropriate default TaskScheduler, and it does not use appropriate default continuation flags. These are all things that await just handles for you.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks for the reply Stephen, just a follow up, I've got the 'public void Start()' method to kick things off when running in debug from Visual Studio, should I be trying to get the awaiter and it's result, or just leave it as service.Start();? – peteski Oct 24 '17 at 09:25
  • 1
    I think you'd have to use `Start()`. Since it doesn't return a task, you can't block on it. – Stephen Cleary Oct 24 '17 at 12:15