When are .NET TaskCompletionSource Tasks eligible for GC (not rooted)?

165 Views Asked by At

I'm writing a utility class for some asynchronous code, and I want to ensure that I don't create memory leaks with the design. Suppose I've got code which executes similarly to the class below. (Obviously you wouldn't write code that works like this, but I've got several code paths which all converge into a single underlying function, and one of those code paths is effectively doing this). The key point in the code is that nothing in my code holds a reference to either the TaskCompletionSource or its Task, though .NET might be doing some voodoo which does root them.

Suppose that Foo instances are created as needed and are not permanently rooted. Is tcs.Task rooted? More importantly, suppose request never completes, so the result doesn't get set. Would tcs.Task hang around forever? What about the continuation task? Or, is their lifetime dependent on the Foo object and they will go away when the Foo instance gets GC-ed?

class Foo
{
    public void Bar(Action<Action<object>> request, Action<T> callback)
    {
        var tcs = new TaskCompletionSource<object>();
        request(result => tcs.SetResult(result));  // this runs asynchronously
        tcs.Task.ContinueWith(task => callback(task.Result));
    }
}
1

There are 1 best solutions below

5
Nick On

Suppose that Foo instances are created as needed and are not permanently rooted. Is tcs.Task rooted?

No. There is no reason for tcs.Task to be routed out of Bar and request. Since you pass tcs in a lambda, it will be "closured", i.e. there will be an object created and it will be passed to both Bar and request. Once both routines end, tcs.Task will become unreachable and legible for collection.

More importantly, suppose request never completes, so the result doesn't get set. Would tcs.Task hang around forever?

If request never completes, you have more problems than just tcs.Task. It means that a whole thread will be blocked and stay alive until the end of your process, with all its stack (1 MB) and all the graph of objects that is rooted in that stack. Damage is worse if you also acquire unmanaged resources. If request fails with an exception, it will end, its stack will get released and the objects that are rooted to it will become legible for collection.