按钮单击处理程序中的启动任务会导致聚合异常

时间:2013-12-15 01:30:24

标签: c# exception task aggregate

请帮助别人。 我想在按钮单击处理程序中创建并启动一个新任务,它总是会导致聚合异常。我正在做以下事情:

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的理解有些不对。提前谢谢。

3 个答案:

答案 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;