Keep UI responsive using Tasks, Handle AggregateException

烂漫一生 提交于 2019-12-13 16:46:25

问题


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.


回答1:


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


来源:https://stackoverflow.com/questions/31052523/keep-ui-responsive-using-tasks-handle-aggregateexception

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!