Reputation: 23833
I have the following code to build an advanced data structure which is pulled from SQL Server, then when the retrevial of that data is complete I update the UI. The code used is
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);
}
This works as it should on my main development machine; that is, it completes the build of the database
object, then in the continuation update the UI and disposes the task
(Task object).
However, I have been doing some testing on another machine and I get an InvalidOperationException
, this is due to the task.Dispose()
on task
which still in the Running
state, but the continuation cont
should never fire unless the task has ran to completion.
Here's what the code looks like in the debugger when the exception is thrown:
I am aware that it almost always unneccessary to call Dispose
on tasks. This question is more about why the continuation is firing at all here?**
Upvotes: 1
Views: 626
Reputation: 74530
The reason for this is simple, you are calling Dispose
on the continuation itself and not on the first task
Your code consists of:
Task task = null;
var task = <task 1>.ContinueWith(t => {
/* task 2 */
task.Dispose();
});
In the above code, task
is equal to the continuation (ContinueWith
doesn't pass back the original Task
, it passes the continuation) and that's what's getting captured in the closure you're passing to ContinueWith
.
You can test this by comparing the references of the Task
parameter passed into the ContinueWith
method with 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();
});
In order to dispose of the first, you need to break it up into two Task
variables and capture the first Task
, like so:
var task1 = <task 1>;
var task2 = task1.ContinueWith(t => {
// Dispose of task1 when done.
using (task1)
{
// Do task 2.
}
});
However, because the previous Task
is passed to you as a parameter in the ContinueWith
method, you don't need to capture task
in the closure at all, you can simply call Dispose
on the Task
passed as a parameter to you:
var task = <task 1>.ContinueWith(t => {
// t = task 1
// task = task 2
// Dispose of task 1 when done.
using (t)
{
// Do task 2.
}
});
Upvotes: 3
Reputation: 1076
I'm pretty sure you are trying to do above is equivelent to:
task = Task.Factory.StartNew(() => ...);
task.ContinueWith(cont => { ... task.Dispose(); });
However, what gets assigned to task variable with your code will be the ContinueWith work item, not the origninal StartNew work item.
More importantly, you probably don't even need to worry about task.Dispose() in this scenario.
The only time there is any real value in doing task.Dispose() is when there is a task.Wait() involved somewhere, which allocates an OS wait handle resource under the covers.
Upvotes: 1