Preserve UI using tasks, handle aggregated exception

I am having a problem handling AggregateException if a WinForms application starts a task to keep it responsive while the task is running.

The simplified case is as follows. Let's assume my form has a rather slow method like:

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 clicking the button, I want my form to show the SlowDivision result (3,4). The following code hangs the UI for some time:

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

      

Hence, I would like to start a task that will handle. When this task ends, it should continue with the action that will show the result. To throw InvalidOperationException I need to make sure label1 is accessible from the thread it was created on, hence 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 do I handle exceptions, for example if I want to calculate SlowDivision (3, 0)?

Typically, if a task throws an unhandled exception, it is dispatched to the waiting thread through an AggregateException. Numerous examples show the following code:

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

      

The problem is this: I can't wait for my task to complete because I want my UI to remain responsive.

Create a continuation task on error that will read Task.Exception and handle fail accordingly:

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);
}

      

The try / catch in the function doesn't help either (as you would expect).

So, how do I properly react to the AggregateExceptions given by the task without waiting for it.

+2


source to share


1 answer


If you can use it .NET 4.5

, then I would use the new one async/await

, which greatly simplifies your code and saves you the trouble of dealing with continuations and AggregateException

s, which just make noise in your code and distract you from focusing on what you are actually trying to accomplish.

It will 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;
}

      

+4


source







All Articles