使用Tasks保持UI响应,处理AggregateException

时间:2015-06-25 13:58:26

标签: c# multithreading task aggregateexception

如果我的WinForms应用程序启动任务以在任务执行时保持响应,我在处理AggregateException时遇到问题。

简化案例如下。 假设我的表单有一个相当慢的方法,例如:

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

按下按钮后,我希望我的表单显示SlowDivision(3,4)的结果。 以下代码将挂起用户界面一段时间:

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

因此,我想开始一项将进行处理的任务。此任务完成后,应继续执行将显示结果的操作。为了防止InvalidOperationException,我需要确保label1是从创建它的线程访问的,因此是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();
        }));
    });
}

到目前为止,这么好,但是如何处理异常,例如如果我想计算SlowDivision(3,0)?

通常,如果任务抛出未处理的异常,则会通过AggregateException将其转发到等待的线程。许多示例显示以下代码:

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

问题是:我不能等待我的任务执行,因为我希望我的UI保持响应。

创建一个故障的任务延续,它会读取Task.Exception并且相应的句柄不起作用:

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

函数中的try / catch也没有帮助(正如预期的那样)。

那么如何在不等待任务的情况下对任务抛出的AggregateExceptions做出正确反应。

1 个答案:

答案 0 :(得分:4)

如果您可以使用DROP TRIGGER,那么我会使用较新的IF (OBJECT_ID(N'[dbo].[trg]') IS NOT NULL) BEGIN DROP TRIGGER [dbo].[trg]; END; ,这会简化代码,并使您免于处理延续和.NET 4.5 s,只是在代码中制造噪音并分散你对你实际想要完成的事情的注意力。

它看起来像这样:

async/await