请帮助别人。 我想在按钮单击处理程序中创建并启动一个新任务,它总是会导致聚合异常。我正在做以下事情:
private void btn_Click(object sender, EventArgs e)
{
Task<Image> t = Task<Image>.Factory.StartNew(InvertImage,
TaskCreationOptions.LongRunning);
t.ContinueWith( task => {
some code here;
pictureBox1.Image = t.Result;
},
TaskContinuationOptions.OnlyOnRanToCompletition);
t.ContinueWith( task => { some code here },
TaskContinuationOptions.OnlyOnFaulted);
}
private Image InvertImage()
{ some code here }
如果在主线程中运行的代码工作正常,那么我对使用Tasks的理解有些不对。提前谢谢。
答案 0 :(得分:2)
默认情况下,continuation在默认调度程序(Threadpool Scheduler)上运行。线程池线程始终是后台线程,因此它们无法更新UI组件(因为UI组件始终在前台线程上运行)。所以你的代码不起作用。
修复:从UI线程获取调度程序。这将确保延续在创建UI组件的同一线程上运行
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
并将其传递给ContinueWith函数。
t.ContinueWith( task => {
some code here;
pictureBox1.Image = t.Result;
},
TaskContinuationOptions.OnlyOnRanToCompletition,scheduler);
答案 1 :(得分:1)
在Winforms(甚至是WPF)中,只有创建组件的线程可以更新它,您应该使代码线程安全。 出于这个原因,调试器引发了一个InvalidOperationException,其消息是“从创建它的线程以外的线程访问控制控件名称”。它被封装为AggregateException,因为任务封装了聚合异常中的所有异常
您可以使用此代码迭代任务引发的聚合异常中的所有异常
try
{
t.Wait();
}
catch (AggregateException ae)
{
// Assume we know what's going on with this particular exception.
// Rethrow anything else. AggregateException.Handle provides
// another way to express this. See later example.
foreach (var e in ae.InnerExceptions)
{
if (e is MyCustomException)
{
Console.WriteLine(e.Message);
}
else
{
throw;
}
}
}
为了使你的线程安全,只需做这样的事情
// If the calling thread is different from the thread that
// created the pictureBox control, this method creates a
// SetImageCallback and calls itself asynchronously using the
// Invoke method.
// This delegate enables asynchronous calls for setting
// the text property on a TextBox control.
delegate void SetPictureBoxCallback(Image image);
// If the calling thread is the same as the thread that created
// the PictureBox control, the Image property is set directly.
private void SetPictureBox(Image image)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.picturebox1.InvokeRequired)
{
SetPictureBoxCallback d = new SetPictureBoxCallback(SetPictureBox);
this.Invoke(d, new object[] { image });
}
else
{
picturebox1.Image= image;
}
}
答案 2 :(得分:0)
在调用线程中使用Task结果的另一个选项是使用async/await
关键字。这样编译器就可以为您捕获正确的TaskScheduler
。看下面的代码。您需要为异常处理添加try/catch
语句。
这样,代码仍然是异步的,但看起来像是同步代码,请记住代码应该是可读的。
var _image = await Task<Image>.Factory.StartNew(InvertImage, TaskCreationOptions.LongRunning);
pictureBox1.Image = _image;