4

NOTE: I'm not saying this is a good idea, just trying to find out if there's a 'better' option than this brute-force one.

This came up in a previous SO thread @ How to get the current task reference?

However, that thread was a bit more constrained by a particular interface.

The brute-force approach I threw together quickly just uses a dictionary of weak references.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace GetCurrentTaskExample
{
    public static class TaskContext
    {
        // don't need a ConcurrentDictionary since we won't be reading/writing the same task id with different tasks concurrently
        private static readonly Dictionary<int, WeakReference> s_indexedTaskReferences = new Dictionary<int, WeakReference>();

        public static void AddAndStartTasks(IEnumerable<Task> tasks)
        {
            foreach (var task in tasks)
            {
                AddTask(task);
                task.Start();
            }
        }

        public static void AddTask(Task task)
        {
            s_indexedTaskReferences[task.Id] = new WeakReference(task);
        }

        public static Task GetCurrentTask()
        {
            var taskId = Task.CurrentId;
            if (taskId == null) return null;

            WeakReference weakReference;
            if (s_indexedTaskReferences.TryGetValue(taskId.Value, out weakReference) == false) return null;
            if (weakReference == null) return null; // should not happen since we don't store null as a value

            var task = weakReference.Target as Task;
            return task;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var tasks = Enumerable.Range(0, 100)
                .Select(i => new Task(VerifyCurrentTaskWorks, i))
                .ToArray();

            TaskContext.AddAndStartTasks(tasks);

            Task.WaitAll(tasks);
        }

        static void VerifyCurrentTaskWorks(object instanceIdentifier)
        {
            var currentTask = TaskContext.GetCurrentTask();

            if (currentTask.Id == Task.CurrentId)
            {
                Console.WriteLine("Verified for instance {0} that Task.CurrentId value of {1} matches Id property {2} of task {3}",
                                  instanceIdentifier, Task.CurrentId, currentTask.Id, currentTask);
            }
            else
            {
                var errorMessage = String.Format("TaskContext.GetCurrentTask() failed for instance {0} with Task.CurrentId value of {1} and currentTask.Id value of {2}",
                                  instanceIdentifier, Task.CurrentId, currentTask.Id);
                throw new InvalidOperationException(errorMessage);
            }
        }
    }
}

However, this clearly means whatever is creating the tasks is forced to deal with this additional headache, so it's not very usable, especially WRT C#5 async methods where the task isn't so explicitly created.

Again, probably a bad idea to have code that needs this, so consider it more akin to a thought exercise. :)

Community
  • 1
  • 1
James Manning
  • 13,429
  • 2
  • 40
  • 64
  • If you're not sure this is a good idea, then why are you trying to do it? – svick Mar 24 '12 at 23:19
  • Also, I think you can't make it C#5 async-friendly, because `Task.CurrentId` inside an async method returns `null`. – svick Mar 24 '12 at 23:26
  • @svick - fascinating - I wouldn't have expected CurrentId to be null in an async method (well, after the first await). Happen to know why that is? – James Manning Mar 25 '12 at 04:21
  • I have no idea. `await` should use `TaskScheduler`, so I would expect `CurrentId` to work too. – svick Mar 25 '12 at 12:23
  • Can I ask why you are trying to do this? – David Nelson Apr 01 '12 at 04:01
  • @DavidNelson - originally I was thinking about doing 'context' data per task in this way (for instance, wanting separating logging instances per task since my logging and task granularity match and log4net has no current support for task-based logging). I probably won't use it as such, though. – James Manning Apr 02 '12 at 23:24

1 Answers1

0

There's not much of a better way, unfortunately, and since the eventual goal was to key off of Task.CurrentId, it's indeed not even useful since we won't have an ability to get 'current task id' for async methods (after the first await, so it's returned to the caller already).

James Manning
  • 13,429
  • 2
  • 40
  • 64