奇数任务并行库InvalidOperationException

时间:2012-09-14 15:53:20

标签: c# .net task-parallel-library dispose

我有以下代码来构建从SQL Server中提取的高级数据结构,然后当该数据的retrevial​​完成时,我更新了UI。使用的代码是

private void BuildSelectedTreeViewSectionAsync(TreeNode selectedNode)
{
    // Initialise.
    SqlServer instance = null;
    SqlServer.Database database = null;

    // Build and expand the TreeNode.
    Task task = null;
    task = Task.Factory.StartNew(() => {
        string[] tmpStrArr = selectedNode.Text.Split(' ');

        string strDatabaseName = tmpStrArr[0];

        instance = SqlServer.Instance(this.conn);

        database = instance.GetDatabaseFromName(strDatabaseName);
    }).ContinueWith(cont => {
        instance.BuildTreeViewForSelectedDatabase(this.customTreeViewSql,
            selectedNode, database);

        selectedNode.Expand();

        task.Dispose();
    }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion,
        this.MainUiScheduler);
}

这在我的主要开发机器上应该工作;也就是说,它完成了database对象的构建,然后在继续更新UI并处理task(任务对象)。

但是,我一直在另一台计算机上进行一些测试而得到一个InvalidOperationException,这是因为task.Dispose()上的task仍处于Running状态但是,除非任务已经完成,否则延续cont永远不会触发。

以下是抛出异常时代码在调试器中的样子:

enter image description here

I am aware that it almost always unneccessary to call Dispose on tasks。这个问题更多的是关于为何继续在这里解雇?**

2 个答案:

答案 0 :(得分:3)

原因很简单,你在继续本身上调用Dispose而在第一个任务上调用而不是

您的代码包含:

Task task = null;
var task = <task 1>.ContinueWith(t => { 
    /* task 2 */ 

    task.Dispose();
});

在上面的代码中,task等于延续(ContinueWith没有传回原始的Task,它通过了延续),这就是在闭包中捕获的内容你传递给ContinueWith

您可以通过将Task方法中传递的ContinueWith参数的引用与task进行比较来测试:

Task task = null;
var task = <task 1>.ContinueWith(t => { 
    /* task 2 */ 
    if (object.ReferenceEquals(t, task))
        throw new InvalidOperationException("Trying to dispose of myself!");

    task.Dispose();
});

为了处理第一个变量,您需要将其分解为两个Task变量并捕获第一个Task,如下所示:

var task1 = <task 1>;
var task2 = task1.ContinueWith(t => {
    // Dispose of task1 when done.
    using (task1)
    {
        // Do task 2.
    }
});

但是,因为previous Task作为ContinueWith方法中的参数传递给您,所以您根本不需要在闭包中捕获task,只需调用Dispose上的Task作为参数传递给您:

var task = <task 1>.ContinueWith(t => {
    // t    = task 1
    // task = task 2
    // Dispose of task 1 when done.
    using (t)
    {
         // Do task 2.
    }
});

答案 1 :(得分:1)

我很确定你在上面尝试做的事情是公平的:

task = Task.Factory.StartNew(() => ...);
task.ContinueWith(cont => { ... task.Dispose(); });

但是,使用您的代码分配给任务变量的内容将是ContinueWith工作项,而不是origninal StartNew工作项。

更重要的是,在这种情况下,您甚至可能不需要担心task.Dispose()。

执行任务时唯一有实际价值的时候.Dispose()是指某个任务涉及的任务.Wait(),它分配了一个操作系统等待处理资源。

更多信息: http://social.msdn.microsoft.com/Forums/en/parallelextensions/thread/7b3a42e5-4ebf-405a-8ee6-bcd2f0214f85