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()になっているためですが、タスクが完了するまで実行されない限り、継続は起動されません。taskRunningcont

例外がスローされたときのデバッガーでのコードは次のようになります。

ここに画像の説明を入力してください

ほとんどの場合Dispose、タスクを呼び出す必要はないことを認識しています。この質問は、なぜ継続がここでまったく発火しているのかについての詳細ですか?**

4

2 に答える 2

3

この理由は単純です。最初のタスクではなくDispose、継続自体を呼び出しています。

コードは次のもので構成されています。

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

    task.Dispose();
});

上記のコードでtaskは、 は継続に等しく (ContinueWith元の を返さず、継続を渡しますTask)、それが、渡したクロージャーでキャプチャされているものですContinueWith

メソッドにTask渡されたパラメーターの参照を次のように比較することで、これをテストできます。ContinueWithtask

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();
});

最初のものを破棄するには、次のように 2 つのTask変数に分割し、最初のものをキャプチャする必要がありますTask

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

ただし、previous Taskはメソッドのパラメーターとして渡されるため、クロージャーでContinueWithキャプチャする必要はまったくありません。渡された をパラメーターとして呼び出すだけで済みます。taskDisposeTask

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(); });

ただし、コードでタスク変数に割り当てられるのは、元の StartNew 作業項目ではなく、ContinueWith 作業項目になります。

さらに重要なことに、このシナリオでは、おそらく task.Dispose() について心配する必要さえありません。

task.Dispose() を実行することに真の価値があるのは、OS 待機ハンドル リソースを内部で割り当てる task.Wait() がどこかに関与している場合だけです。

詳細: http://social.msdn.microsoft.com/Forums/en/parallelextensions/thread/7b3a42e5-4ebf-405a-8ee6-bcd2f0214f85

于 2012-09-14T16:30:20.187 に答える