代码之家  ›  专栏  ›  技术社区  ›  MoonKnight

奇数任务并行库InvalidOperationException

  •  1
  • MoonKnight  · 技术社区  · 14 年前

    我有以下代码来构建一个从SQL Server中提取的高级数据结构,然后当该数据的检索完成时,我会更新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() 在…上 任务 仍然在 Running 状态,但延续 cont 除非任务已经完成,否则永远不要开火。

    以下是抛出异常时调试器中的代码外观:

    enter image description here

    I am aware that it almost always unneccessary to call Dispose on tasks 。这个问题更多的是关于为什么延续在这里**

    2 回复  |  直到 14 年前
        1
  •  3
  •   casperOne    14 年前

    原因很简单,您正在呼叫 Dispose 关于延续本身和 不 关于第一项任务

    您的代码包括:

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

    在上述代码中, 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();
    });
    

    为了处理第一个,你需要把它一分为二 任务 变量并捕获第一个 任务 ,就像这样:

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

    然而,由于 previous Task 作为中的参数传递给您 继续 方法,不需要捕获 任务 在闭包中,您可以简单地调用 处置 在 任务 作为参数传递给您:

    var task = <task 1>.ContinueWith(t => {
        // t    = task 1
        // task = task 2
        // Dispose of task 1 when done.
        using (t)
        {
             // Do task 2.
        }
    });
    
        2
  •  1
  •   Jorgen Thelin    14 年前

    我敢肯定,你试图做的上述事情相当于:

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

    但是,代码中分配给任务变量的将是ContinueWith工作项,而不是原始的StartNew工作项。

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

    做task.Dispose()唯一有实际价值的时候是某个地方涉及到task.Wet(),它在后台分配操作系统等待句柄资源。

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