【问题标题】:Keep UI responsive using Tasks, Handle AggregateException使用任务保持 UI 响应,处理 AggregateException
【发布时间】:2015-06-25 13:58:25
【问题描述】:

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

简化的情况如下。 假设我的 Form 有一个相当慢的方法,例如:

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 做出正确的反应。

【问题讨论】:

  • 使用错误的延续是正确的方法。究竟什么是“不工作”?
  • 您使用的是 .NET 4.5 吗?使用 async/await 会让你的生活更轻松。

标签: c# multithreading task aggregateexception


【解决方案1】:

如果您可以使用.NET 4.5,那么我会使用较新的async/await,它大大简化了代码,并且使您不必处理延续和AggregateExceptions,这只会在代码中产生噪音并分散您的注意力,让您无法专注于您真正想要完成的事情。

看起来像这样:

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

【讨论】:

  • 谢谢,我的印象是使用 async / await 时无法保持 UI 响应。我试过你的解决方案,它有效。
  • 糟糕,反应太快了。确实 UI 保持响应,但是异常没有在 catch (Exception ex) 块中捕获
  • 嗯。这很奇怪。我对其进行了测试,并且能够很好地捕捉到异常。这就是await 关键字的好处:它透明地展开并抛出异常,就好像从来没有线程切换一样。您是否介意将您刚刚尝试的确切代码添加到原始帖子中,看看我是否可以看到为什么异常不会被捕获?
  • 有没有可能是因为你在 VS 中以调试模式运行它?所以它停止了抛出异常的代码,说它没有处理?如果是这样,那真的只是因为您正在调试代码。如果你点击Continue,你会看到异常处理得很好。如果您尝试在不调试的情况下运行它,您会看到它按预期运行。或者,您可以更改 Debug --> Exceptions... 中的一些设置,以阻止 VS 中断它认为是“未处理的异常”。
  • 在 .NET 4.7 中,当尝试设置 textBox 的值时,我得到此代码的错误线程异常。
猜你喜欢
  • 2023-03-07
  • 1970-01-01
  • 1970-01-01
  • 2011-05-31
  • 1970-01-01
  • 2018-06-03
  • 2014-01-26
  • 2019-05-03
  • 2021-07-18
相关资源
最近更新 更多