C# 使用任务保持UI响应,处理聚合异常

C# 使用任务保持UI响应,处理聚合异常,c#,multithreading,task,aggregateexception,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");

如果我的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;
}
按下一个按钮后,我希望我的表单显示慢速分割(3,4)的结果。 以下代码将使用户界面挂起一段时间:

private void button1_Click(object sender, EventArgs e)
{
    this.label1.Text = this.SlowDivision(3, 4).ToString();
}
因此,我想启动一个任务来进行处理。此任务完成后,应继续执行一个将显示结果的操作。为了防止InvalidOperationException,我需要确保label1是从创建它的线程访问的,因此是一个控件。调用:

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();
        }));
    });
}
到目前为止,还不错,但是如何处理异常,例如,如果我想计算慢速分割(3,0)

通常,如果任务引发未处理的异常,则会通过AggregateException将其转发给等待的线程。大量示例显示了以下代码:

var myTask = Task.Factory.StartNew ( () => ...);
try
{
    myTask.Wait();
}
catch (AggregateException exc)
{
    // handle exception
}
问题是:我不能等待我的任务执行,因为我希望我的UI保持响应

在将读取task的faulted上创建任务延续。异常和相应的句柄不起作用:

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也没有帮助(这是可以预期的)


因此,我如何在不等待的情况下对任务引发的聚合异常做出正确反应。

如果您可以使用
.NET 4.5
,那么我将使用较新的
async/await
,这将大大简化代码,并使您不必处理连续性和
AggregateException
,这只会在代码中制造噪音,分散您的注意力,使您无法专注于实际要完成的任务

它看起来像这样:

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

使用错误的延续是正确的方法。什么是“不工作”?你在.NET4.5上吗?使用async/await将使您的生活更轻松。Thanx,我的印象是,在使用async/await时,您无法保持UI的响应性。我试过你的解决方案,效果很好。哎呀,反应太快了。的确,UI保持响应,但是在catch(exception ex)blockHmm中不会捕获异常。真奇怪。我测试了它,能够很好地捕获异常。这就是
wait
关键字的好处:它透明地打开并抛出异常,就好像从来没有线程切换一样。您是否介意将刚才尝试的代码添加到原始帖子中,看看我是否可以理解为什么不会捕获异常?是否可能是因为您在调试模式下以VS运行它?所以它会停止抛出异常的代码,说它未处理?如果是这样的话,那真的只是因为你在调试代码。如果点击
继续
,您将看到异常处理得很好。如果您尝试在不进行调试的情况下运行它,您将看到它按预期运行。或者,您可以在
Debug-->Exceptions…
中更改一些设置,以阻止VS破坏它认为是“未处理的异常”。在.NET 4.7中,当尝试设置文本框的值时,我得到了错误的线程异常。