简体   繁体   中英

Keep UI responsive using Tasks, Handle AggregateException

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.

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 AggregateException s, 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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM