18

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.

svick
  • 236,525
  • 50
  • 385
  • 514
Reuven Bass
  • 672
  • 1
  • 6
  • 16
  • 2
    Can you pass it as a parameter to `SomeImplementation`'s constructor? Even better IMO, pass `MyState` to the constructor and not require `Task` knowledge within `MethodFromSomeInterface` at all. – Stephen Cleary Jul 14 '11 at 23:00
  • @Stephen Cleary, Seems like he can't change the interface. – Filip Ekberg Jul 14 '11 at 23:02
  • I can't change the interface, nor the implementation. So, I do need to associate `MyState` instance with the current `Task`. – Reuven Bass Jul 14 '11 at 23:14
  • Moreover, `MethodFromSomeInterface` may be called concurrently within different tasks. – Reuven Bass Jul 14 '11 at 23:25

5 Answers5

10

Since you can't change the interface nor the implementation, you'll have to do it yourself, e.g., using ThreadStaticAttribute:

static class SomeInterfaceTask
{
  [ThreadStatic]
  static Task Current { get; set; }
}

...

ISomeInterface impl = new SomeImplementation();
Task task = null;
task = Task.Factory.StartNew(() =>
{
  SomeInterfaceTask.Current = task;
  impl.MethodFromSomeInterface();
}, new MyState());

...

void MethodFromSomeInterface()
{
  Task currentTask = SomeInterfaceTask.Current;
  MyState state = (MyState) currentTask.AsyncState();
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 2
    Is this really thread safe? In fact, is anything beside using lambda parameter in `StartNew` thread safe? It seems to me that the `task` variable could get out of scope before the lambda even runs. – wilx Nov 29 '11 at 09:45
  • 1
    ThreadStatic is a different thing than TaskStatic would be, if it existed. This may be safe with the default scheduler, but there is no guarantee that a thread will not be reused for multiple tasks, in which case this does not have the protection you may be expecting. This might be the worst kind of bug -- looks good, runs good (usually), and then somewhere down there is a difficult-to-reproduce error. – danwyand Jun 06 '13 at 18:53
  • 1
    With this code, `Current` is always set (on the same thread) immediately before `MethodFromSomeInterface` is invoked. It doesn't matter if the thread is reused for another task. – Stephen Cleary Jun 06 '13 at 19:29
  • @StephenCleary that is incorrect -- there is no locking around the task variable being set and being read. Using this model, you could set the task in the Current variable; then before the method is invoked, another task is started on the same thread and the same memory location is overwritten. The first task will then read the wrong value. If a particular TPL scheduler is non-preemptive, this will indeed work; but that is not a safe assumption to make in this context. – danwyand Jun 24 '13 at 20:16
  • 2
    I see what you're saying now. But the thread pool will never do that; any (fully-synchronous) work queued to the thread pool, once it has started, will always run to completion. To implement "suspend the currently-running task and run another task instead on this same thread" would be incredibly complex and result in *worse* performance *and* throughput, so there is no reason to believe that the thread pool will ever do that. – Stephen Cleary Jun 24 '13 at 22:11
  • @StephenCleary As you said, once the task started, it will always run to completion in the specified thread. But in the async/await model, the task seems will yield the current thread when meet a block operation (the await keyword), and continued on the `SynchronizationContext`. So, when the task yields the current thread and next task picks up the thread and sets the `SomeInterfaceTask.Current`, then the previous task is resumed on its starting thread (or another thread) the `SomeInterfaceTask.Current` will be changed. – JasonMing Aug 17 '14 at 14:38
  • 5
    There's a race condition between setting task object in `task = Task.Factory.StartNew...` and `SomeInterfaceTask.Current = task;`... – thab Oct 01 '15 at 13:01
  • @thab Can you provide a sample of the race condition :-) ? – Legends Aug 29 '16 at 23:33
  • @danwyand Can you provide a sample of the race condition ? – Legends Aug 29 '16 at 23:33
  • Imagine you have a class with a static variable, and an instance method that prints the variable. Obviously, `Bad.STATIC = "message"; new Bad().printMessage();` is problematic with multiple threads. This does the same thing -- that `[ThreadStatic]` attribute does nothing to help, since multiple _tasks_ can run in the same _thread_. Not really a race condition -- just an unsafe/incorrect use of static. – danwyand Aug 30 '16 at 02:57
  • I know what you mean, but in this case I don't get a race condition: Here is a [fiddle](https://dotnetfiddle.net/v5ffit). Copy&Execute locally. – Legends Aug 30 '16 at 07:42
  • Well actually, I reckon the race condition is between the assigning of the task variable in `task = Task.Factory.StartNew(` and the copying of the task variable in `SomeInterfaceTask.Current = task;`. There's nothing to guarantee the order that I can see... If you started it in two steps it would work (e.g. `var task = new Task(() => { SomeInterfaceTask.Current = task; }); task.Start(Task.Factory.Scheduler);`) – thab Aug 30 '16 at 15:16
4

If you can use .NET 4.6 or greater, .NET Standard or .NET Core, they've solved this problem with AsyncLocal. https://learn.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));
Seth
  • 1,064
  • 11
  • 18
3

Here is a "hacky" class that can be used for that.
Just use the CurrentTask property to get the current running Task.
I strongly advise against using it anywhere near production code!

public static class TaskGetter
{
    private static string _propertyName;
    private static Type _taskType;
    private static PropertyInfo _property;
    private static Func<Task> _getter;

    static TaskGetter()
    {
        _taskType = typeof(Task);
        _propertyName = "InternalCurrent";
        SetupGetter();
    }

    public static void SetPropertyName(string newName)
    {
        _propertyName = newName;
        SetupGetter();
    }

    public static Task CurrentTask
    {
        get
        {
            return _getter();
        }
    }

    private static void SetupGetter()
    {
        _getter = () => null;
        _property = _taskType.GetProperties(BindingFlags.Static | BindingFlags.NonPublic).Where(p => p.Name == _propertyName).FirstOrDefault();
        if (_property != null)
        {
            _getter = () =>
            {
                var val = _property.GetValue(null);
                return val == null ? null : (Task)val;
            };
        }
    }
}
hofi
  • 101
  • 1
  • 3
  • This is a good kind of hacky and it will break early (resolving the property) so it's not that bad. AsyncState (and random data stores with no relation to namespaces in general) is not something anyone should ever use but, if it was, I would be fine using this in production. Thumbs up. – Seth Jun 29 '18 at 02:55
2

The following example shows how it can be achieved, resolving the issue with the answer provided by @stephen-cleary. It is a bit convoluted but essentially the key is in the TaskContext class below which uses CallContext.LogicalSetData, CallContext.LogicalGetData and CallContext.FreeNamedDataSlot which are useful for creating your own Task contexts. The rest of the fluff is to answer the OP's question:

class Program
{
    static void Main(string[] args)
    {
        var t1 = Task.Factory.StartNewWithContext(async () => { await DoSomething(); });
        var t2 = Task.Factory.StartNewWithContext(async () => { await DoSomething(); });

        Task.WaitAll(t1, t2);
    }

    private static async Task DoSomething()
    {
        var id1 = TaskContext.Current.Task.Id;
        Console.WriteLine(id1);
        await Task.Delay(1000);

        var id2 = TaskContext.Current.Task.Id;
        Console.WriteLine(id2);
        Console.WriteLine(id1 == id2);
    }
}

public static class TaskFactoryExtensions
{
    public static Task StartNewWithContext(this TaskFactory factory, Action action)
    {
        Task task = null;

        task = new Task(() =>
        {
            Debug.Assert(TaskContext.Current == null);
            TaskContext.Current = new TaskContext(task);
            try
            {
                action();
            }
            finally
            {
                TaskContext.Current = null;
            }
        });

        task.Start();

        return task;
    }

    public static Task StartNewWithContext(this TaskFactory factory, Func<Task> action)
    {
        Task<Task> task = null;

        task = new Task<Task>(async () =>
        {
            Debug.Assert(TaskContext.Current == null);
            TaskContext.Current = new TaskContext(task);
            try
            {
                await action();
            }
            finally
            {
                TaskContext.Current = null;
            }
        });

        task.Start();

        return task.Unwrap();
    }
}

public sealed class TaskContext
{
    // Use your own unique key for better performance
    private static readonly string contextKey = Guid.NewGuid().ToString();

    public TaskContext(Task task)
    {
        this.Task = task;
    }

    public Task Task { get; private set; }

    public static TaskContext Current
    {
        get { return (TaskContext)CallContext.LogicalGetData(contextKey); }
        internal set
        {
            if (value == null)
            {
                CallContext.FreeNamedDataSlot(contextKey);
            }
            else
            {
                CallContext.LogicalSetData(contextKey, value);
            }
        }
    }
}
Ananke
  • 1,250
  • 9
  • 11
0

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();
Tim Lovell-Smith
  • 15,310
  • 14
  • 76
  • 93