2

I have a problem with handling the AggregateException if my WinForms application starts a task to keep responsive while the task is performing.

The simplified case is as follows. Suppose my Form has a fairly slow method, for instance:

private double SlowDivision(double a, double b)
{
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
    if (b==0) throw new ArgumentException("b");
    return a / b;
}

After pressing a button I want my form to show the result of SlowDivision(3,4). The following code would hang the user interface for some time:

private void button1_Click(object sender, EventArgs e)
{
    this.label1.Text = this.SlowDivision(3, 4).ToString();
}

Hence I'd like to start a task that will do the processing. When this task finishes, it should continue with an action that will display the result. To prevent an InvalidOperationException I need to be sure that label1 is accessed from the thread that it was created on, hence a Control.Invoke:

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew ( () =>
    {
        return this.SlowDivision(3, 4);
    })
    .ContinueWith( (t) =>
    {
        this.Invoke( new MethodInvoker(() => 
        {
            this.label1.Text = t.Result.ToString();
        }));
    });
}

So far, so good, but how to handle exceptions, for instance If I want to calculate SlowDivision(3, 0)?

Normally if a task throws an unhandled exception, it is forwarded to a waiting thread via an AggregateException. Numerous examples show the following code:

var myTask = Task.Factory.StartNew ( () => ...);
try
{
    myTask.Wait();
}
catch (AggregateException exc)
{
    // handle exception
}

The problem is: I can't wait for my Task to execute, because I want my UI to remain responsive.

Create a task continuation on faulted that would read Task.Exception and handle accordingly doesn't work:

private void button1_Click(object sender, EventArgs e)
{
    var slowDivTask = Task.Factory.StartNew(() =>
    {
       return this.SlowDivision(3, 0);
    });

    slowDivTask.ContinueWith((t) =>
    {
        this.Invoke(new MethodInvoker(() =>
        {
            this.label1.Text = t.Result.ToString();
        }));
    }, TaskContinuationOptions.NotOnFaulted);

    slowDivTask.ContinueWith((t) =>
    {
        AggregateException ae = t.Exception;
        ae.Handle(exc =>
        {
            // handle the exception
            return true;
        });
    }, TaskContinuationOptions.OnlyOnFaulted);
}

A try / catch in the function also doesn't help (as could be expected).

So how do I react properly on AggregateExceptions thrown by the task without waiting for it.

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116

1 Answers1

6

If you can use .NET 4.5, then I would use the newer async/await, which simplifies the code a lot, and saves you from having to deal with continuations and AggregateExceptions, which just create noise in the code and distract you from focusing on what you are actually trying to accomplish.

It would look something like this:

private async void button1_Click(object sender, EventArgs e)
{
    try
    {
        double result = await Task.Run(() => this.SlowDivision(3, 0));
        this.Label1.Text = result.ToString();
    }
    catch (Exception ex)
    {
        this.textBox1.Text = ex.ToString();
    }
}

private double SlowDivision(double a, double b)
{
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
    if (b == 0) throw new ArgumentException("b");
    return a / b;
}
sstan
  • 35,425
  • 6
  • 48
  • 66
  • 1
    Thanx, I was under the impression that you couldn't keep the UI responsive when using async / await. I tried your solution and it works. – Harald Coppoolse Jun 26 '15 at 09:37
  • Oops, reacted too fast. Indeed the UI keeps responsive, however the exception is not caught in the catch (Exception ex) block – Harald Coppoolse Jun 26 '15 at 09:45
  • Hmm. That's odd. I tested it and was able to catch the exception just fine. That's the nice thing about the `await` keyword: it transparently unwraps and throws the exceptions as if there was never a thread switch. Do you mind adding the exact code you just tried to your original post, see if I can see why the exception would not get caught? – sstan Jun 26 '15 at 12:08
  • 1
    Is it possible that it's because you are running it in VS in debug mode? So it stops the code where the exception is thrown saying that it's unhandled? If it's that, then it's really just because you are debugging the code. If you hit `Continue`, you'll see that the exception is handled fine. If you try to run it without debugging, you'll see that it runs as expected. Alternatively, you can change some settings in `Debug --> Exceptions...` to stop VS from breaking on what it thinks is an "unhandled exception". – sstan Jun 26 '15 at 12:15
  • In .NET 4.7 I get the wrong thread exception for this code when trying to set the value of a textBox. – Lokiare Nov 22 '19 at 19:51