1

我有以下代码来构建从 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()taskwhich 上仍然处于状态,但是除非任务已经完成,否则不会触发Running延续。cont

抛出异常时,调试器中的代码如下所示:

在此处输入图像描述

我知道调用Dispose任务几乎总是不必要的。这个问题更多地是关于为什么继续在这里触发?**

4

2 回答 2

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在闭包中捕获,您可以简单地调用作为参数传递给您的参数DisposeTask

var task = <task 1>.ContinueWith(t => {
    // t    = task 1
    // task = task 2
    // Dispose of task 1 when done.
    using (t)
    {
         // Do task 2.
    }
});
于 2012-09-14T16:32:13.467 回答
1

我很确定您在上面尝试做的事情等同于:

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

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

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

唯一一次执行 task.Dispose() 有任何实际价值是当某处涉及 task.Wait() 时,它会在后台分配 OS 等待句柄资源。

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

于 2012-09-14T16:30:20.187 回答