使用任务保持 UI 响应,处理 AggregateException
Keep UI responsive using Tasks, Handle 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 的情况下做出正确反应。
如果你可以使用 .NET 4.5
,那么我会使用较新的 async/await
,它大大简化了代码,并使你不必处理延续和 AggregateException
s ,这只会在代码中制造噪音并分散您对实际要完成的任务的注意力。
看起来像这样:
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;
}
如果我的 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 的情况下做出正确反应。
如果你可以使用 .NET 4.5
,那么我会使用较新的 async/await
,它大大简化了代码,并使你不必处理延续和 AggregateException
s ,这只会在代码中制造噪音并分散您对实际要完成的任务的注意力。
看起来像这样:
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;
}