How to get the current task reference?

Viewed 22275

How can I get reference to the task my code is executed within?

ISomeInterface impl = new SomeImplementation();
Task.Factory.StartNew(() => impl.MethodFromSomeInterface(), new MyState());

...

void MethodFromSomeInterface()
{
    Task currentTask = Task.GetCurrentTask();    // No such method?
    MyState state = (MyState) currentTask.AsyncState();
}

Since I'm calling some interface method, I can't just pass the newly created task as an additional parameter.

5 Answers

If you can use .NET 4.6 or greater, .NET Standard or .NET Core, they've solved this problem with AsyncLocal. https://docs.microsoft.com/en-gb/dotnet/api/system.threading.asynclocal-1?view=netframework-4.7.1

If not, you need to setup a data store somewhen prior to it's use and access it via a closure, not a thread or task. ConcurrentDictionary will help cover up any mistakes you make doing this.

When code awaits, the current task releases the thread - i.e. threads are unrelated to tasks, in the programming model at least.

Demo:

// I feel like demo code about threading needs to guarantee
// it actually has some in the first place :)
// The second number is IOCompletionPorts which would be relevant
// if we were using IO (strangely enough).
var threads = Environment.ProcessorCount * 4;
ThreadPool.SetMaxThreads(threads, threads);
ThreadPool.SetMinThreads(threads, threads);

var rand = new Random(DateTime.Now.Millisecond);

var tasks = Enumerable.Range(0, 50)
    .Select(_ =>
    {
        // State store tied to task by being created in the same closure.
        var taskState = new ConcurrentDictionary<string, object>();
        // There is absolutely no need for this to be a thread-safe
        // data structure in this instance but given the copy-pasta,
        // I thought I'd save people some trouble.

        return Task.Run(async () =>
        {
            taskState["ThreadId"] = Thread.CurrentThread.ManagedThreadId;
            await Task.Delay(rand.Next() % 100);
            return Thread.CurrentThread.ManagedThreadId == (int)taskState["ThreadId"];
        });
    })
    .ToArray();

Task.WaitAll(tasks);
Console.WriteLine("Tasks that stayed on the same thread: " + tasks.Count(t => t.Result));
Console.WriteLine("Tasks that didn't stay on the same thread: " + tasks.Count(t => !t.Result));

If you could change the interface (which was not a constraint for me when I had a similar problem), to me it seemed like Lazy<Task> could be used to solve this OK. So I tried it out.

It works, at least for what I want 'the current task' to mean. But it is subtle code, because AsyncMethodThatYouWantToRun has to do Task.Yield().

If you don't yield, it will fail with System.AggregateException: 'One or more errors occurred. (ValueFactory attempted to access the Value property of this instance.)'

Lazy<Task> eventuallyATask = null; // silly errors about uninitialized variables :-/
eventuallyATask = new Lazy<Task>(
    () => AsyncMethodThatYouWantToRun(eventuallyATask));

Task t = eventuallyATask.Value; // actually start the task!

async Task AsyncMethodThatYouWantToRun(Lazy<Task> lazyThisTask)
{
    await Task.Yield(); // or else, the 'task' object won't finish being created!

    Task thisTask = lazyThisTask.Value;
    Console.WriteLine("you win! Your task got a reference to itself");
}

t.Wait();

Alternatively instead of the subtlety of Task.Yield we could just go tasks all the way, and use TaskCompletionSource<Task> to solve it. (eliminating any potential errors/deadlocks, since our task safely releases the thread until it can know itself!)

    var eventuallyTheTask = new TaskCompletionSource<Task>();
    Task t = AsyncMethodThatYouWantToRun(eventuallyTheTask.Task); // start the task!
    eventuallyTheTask.SetResult(t); //unblock the task and give it self-knowledge

    async Task AsyncMethodThatYouWantToRun(Task<Task> thisTaskAsync)
    {
        Task thisTask = await thisTaskAsync; // gets this task :)
        Console.WriteLine("you win! Your task got a reference to itself (== 't')");
    }

    t.Wait();
Related