25

I'm going to use lots of tasks running on my application. Each bunch of tasks is running for some reason. I would like to name these tasks so when I watch the Parallel Tasks window, I could recognize them easily.

With another point of view, consider I'm using tasks at the framework level to populate a list. A developer that use my framework is also using tasks for her job. If she looks at the Parallel Tasks Window she will find some tasks having no idea about. I want to name tasks so she can distinguish the framework tasks from her tasks.

It would be very convenient if there was such API:

var task = new Task(action, "Growth calculation task")

or maybe:

var task = Task.Factory.StartNew(action, "Populating the datagrid")

or even while working with Parallel.ForEach

Parallel.ForEach(list, action, "Salary Calculation Task"

Is it possible to name a task?

Is it possible to give ‍‍‍Parallel.ForEach a naming structure (maybe using a lambda) so it creates tasks with that naming?

Is there such API somewhere that I'm missing?


I've also tried to use an inherited task to override it's ToString(). But unfortunately the Parallel Tasks window doesn't use ToString()!

class NamedTask : Task
{
    private string TaskName { get; set; }
    public NamedTask(Action action, string taskName):base(action)
    {
        TaskName = taskName;
    }

    public override string ToString()
    {
        return TaskName;
    }
}
mehrandvd
  • 8,806
  • 12
  • 64
  • 111

12 Answers12

35

You could relate any object with any object. Here is an extension for Task. It uses a WeakReference so the task can still be garbage collected when all references are out of scope.

Usage:

var myTask = new Task(...
myTask.Tag("The name here");
var nameOfTask = (string)myTask.Tag();

Extension class:

public static class TaskExtensions
{
    private static readonly Dictionary<WeakReference<Task>, object> TaskNames = new Dictionary<WeakReference<Task>, object>(); 

    public static void Tag(this Task pTask, object pTag)
    {
        if (pTask == null) return;
        var weakReference = ContainsTask(pTask);
        if (weakReference == null)
        {
            weakReference = new WeakReference<Task>(pTask);
        }
        TaskNames[weakReference] = pTag;
    }

    public static object Tag(this Task pTask)
    {
        var weakReference = ContainsTask(pTask);
        if (weakReference == null) return null;
        return TaskNames[weakReference];
    }

    private static WeakReference<Task> ContainsTask(Task pTask)
    {
        foreach (var kvp in TaskNames.ToList())
        {
            var weakReference = kvp.Key;

            Task taskFromReference;
            if (!weakReference.TryGetTarget(out taskFromReference))
            {
                TaskNames.Remove(weakReference); //Keep the dictionary clean.
                continue;
            }

            if (pTask == taskFromReference)
            {
                return weakReference;
            }
        }
        return null;
    }
}
Mike de Klerk
  • 11,906
  • 8
  • 54
  • 76
  • @mehrandvd I think this should be the accepted answer :) – Mike de Klerk Nov 17 '17 at 19:09
  • 3
    really like this one, but why not use ConcurrentDictionary? – Chris Feb 07 '20 at 08:07
  • @Harry I think it depends on how the Tasks are tagged. Is that done in an async process? If that is the case then using a lock or ConcurrentDictionary is needed indeed. In my experience, tasks are not created (and thus Tagged/named) in async code by default. – Mike de Klerk Feb 07 '20 at 12:57
  • 1
    It might also make sense for Tag() to return Task rather than void - this way you can chain the calls. – Dmitry Streblechenko Apr 14 '21 at 04:44
14

You can't really name a Task, but you can name the method which is executed by a Task, which is then shown in the Parallel Tasks windows. So, if naming the Tasks is important for you, don't use lambdas, use normal named methods.

Surprisingly, this works even with Parallel, even though there the Task isn't executing your method directly. I think this is because Parallel Tasks somehow knows about Tasks from Parallel and handles them differently.

svick
  • 236,525
  • 50
  • 385
  • 514
  • 10
    that is nice but in the very often case when you have multiple tasks executing the same method this is not very helpful anyway – Adrian Zanescu Dec 10 '12 at 15:51
  • It's a good solution, and a practical one. Thanks. But It was better to create names. for example, calculation of person A, calculation of person B, ... A parametric naming would be better... But my fundamental question is that why isn't there a simple name property on the tasks there! – mehrandvd Dec 13 '12 at 20:38
3

You can't name tasks.

The task library is internally using a thread pool, so the threads can't be named. Also your inheritance approach won't work, because methods like ".ContinueWith()" will always create a new task, which won't inherit from your class.

  • 1
    Currently I don't need ContinueWith() to continue with a task of my type NamedTask. I just want my task to have a name. I know Tasks use threads internally, but what should make us not to name a task? It seems logical tasks to have a name. – mehrandvd Dec 07 '12 at 15:07
  • Because it's not logical for tasks to have a name. Tasks are used for short async operations, not for long requests. – JustAnotherUserYouMayKnow Dec 07 '12 at 17:04
2

I dont think you can name the tasks. You can use Task.Id to track the tasks.

varun257
  • 296
  • 2
  • 17
2

If you only need to know the name of the task after the task is finished then you could just pass it as a parameter. Return it as a part of the task result.

    private async Task<string[]> MyTask(int x, string taskName)
    {
        return new[]
        {
            taskName, x.ToString()
        };
    }

Or map your tasks to a dictionary

        var mapping = new Dictionary<Task, string>();
        var task = new Task(() => Console.WriteLine("myNullTask"));
        mapping.Add(task, "myNullTask");
        foreach (var taskX in mapping)
        {
            Console.WriteLine(
                $"Task Id: {taskX.Key.Id}, " +
                $"Task Name: {taskX.Value}, " +
                $"Task Status: {taskX.Key.Status}");
        }
r3verse
  • 1,000
  • 8
  • 19
1

I'm shooting blind here as I don't know the behavior of Parallel Tasks window but if it uses the debugger api adding a DebuggerDisplay attribute on your NamedTask subclass might help

Adrian Zanescu
  • 7,907
  • 6
  • 35
  • 53
  • Thanks. Nice attribute. It may help, I'll check it. But the question remains. Is tasks something that can't have a name? why? – mehrandvd Dec 07 '12 at 15:10
  • I've checked the attribute. The Parallel Tasks window doesn't use it. Besides we could override the ToString() instead of using this attribute. – mehrandvd Dec 13 '12 at 20:41
0
public class NamesTask {
    readonly Queue<Task> _taskqueue = new Queue<Task>();
    private readonly object _queueLock = new object();

    public Task RunTask(Action action) {
        //incoming task must be queued as soon as it arrives
        var inComingTask = new Task(action);

        lock (_queueLock) {
            _taskqueue.Enqueue(inComingTask);
        }

        return Task.Factory.StartNew(() => {
            //run all actions one by one..
            while (true) {
                lock (_queueLock) { //only one task must be performed at a 
                    if (_taskqueue.Count == 0) return;

                    var outTask = _taskqueue.Dequeue();

                    outTask.Start();
                    outTask.Wait();

                    Console.WriteLine("done....");
                }
            }
        });
    }
}
Dovydas Šopa
  • 2,282
  • 8
  • 26
  • 34
0

I thought of having a dictionary to aid debugging, etc.

Here's a sample of what I have been doing:

private static void Main(string[] args)
{
    var tasksIdDic = new ConcurrentDictionary<int?, string>();
    Random rnd = new Random(DateTime.Now.Millisecond);
    var tasks = new List<Task>();

    tasks.Add(Task.Run(() =>  
    {
        Task.Delay(TimeSpan.FromSeconds(rnd.Next(1, 5))).Wait();
        tasksIdDic.TryAdd(Task.CurrentId, "First");

        Console.WriteLine($"{tasksIdDic[Task.CurrentId]} completed.");
    }));

    tasks.Add(Task.Run(() =>
    {
        Task.Delay(TimeSpan.FromSeconds(rnd.Next(1, 5))).Wait();
        tasksIdDic.TryAdd(Task.CurrentId, "Second");

        Console.WriteLine($"{tasksIdDic[Task.CurrentId]} completed.");
    }));

    tasks.Add(Task.Run(() =>
    {
        Task.Delay(TimeSpan.FromSeconds(rnd.Next(1, 5))).Wait();
        tasksIdDic.TryAdd(Task.CurrentId, "Third");

        Console.WriteLine($"{tasksIdDic[Task.CurrentId]} completed.");
    }));

   //do some work - there is no guarantee, but assuming you add the task names to the dictionary at the very beginning of each thread, the dictionary will be populated and be of benefit sometime soon after the start of the tasks.
   //Task.Delay(TimeSpan.FromSeconds(5)).Wait();

    //wait for all just so I see a console output
    Task.WaitAll(tasks.ToArray());
}

enter image description here

Veverke
  • 9,208
  • 4
  • 51
  • 95
0

Thanks to mike's answer I ended up with:

    public static class ExtensionMethods
    {
        private static readonly ConcurrentDictionary<WeakReference<Task>, object> TaskNames = new ConcurrentDictionary<WeakReference<Task>, object>();

        public static void _Tag(this Task pTask, object pTag)
        {
            if (pTask == null) return;
            var weakReference = ContainsTask(pTask) ?? new WeakReference<Task>(pTask);
            TaskNames[weakReference] = pTag;
        }
        public static void _Name(this Task pTask, string name)
        {
            _Tag(pTask, name);
        }

        public static object _Tag(this Task pTask)
        {
            var weakReference = ContainsTask(pTask);
            if (weakReference == null) return null;
            return TaskNames[weakReference];
        }
        public static object _Name(this Task pTask)
        {
            return (string)_Tag(pTask);
        }

        private static WeakReference<Task> ContainsTask(Task pTask)
        {
            foreach (var kvp in TaskNames.ToList())
            {
                WeakReference<Task> weakReference = kvp.Key;

                if (!weakReference.TryGetTarget(out var taskFromReference))
                {
                    TaskNames.TryRemove(weakReference, out _);
                    //TaskNames.TryRemove(out ); //Keep the dictionary clean.
                    continue;
                }

                if (pTask == taskFromReference)
                {
                    return weakReference;
                }
            }
            return null;
        }
    }

It is thread safe now and it also supports a name not just a tag.

Tono Nam
  • 34,064
  • 78
  • 298
  • 470
0

In case this is helpful for anyone, I addressed the above issue as follows:

public static class NamedTasks
{
    public static Dictionary<string, Task> ActiveTasks { get; private set; } = new Dictionary<string, Task>();

    public static void Run(string name, Action action)
    {
        var task = new Task(action);
        ActiveTasks.Add(name, task);
        task.Start();

    public static void RemoveTask(string name)
    {
        if (ActiveTasks.ContainsKey(name))
            ActiveTasks.Remove(name);
    }
}

Usage:

// Start new named task
var taskName = "<code file> - <method>";
NamedTasks.Run(taskName, () =>
{
    // do stuff
    NamedTasks.RemoveTask(taskName);
});

...

// Print names of active tasks
var taskNames = NamedTasks.ActiveTasks.Keys.ToList();
foreach (var taskName in taskNames)
    if (NamedTasks.ActiveTasks[taskName].Status == TaskStatus.Running)
        Console.WriteLine(taskName);

This worked quite nicely for me.

Kees van Zon
  • 191
  • 1
  • 1
  • 8
  • There is already a built-in [`TaskFactory`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory) class. Creating a class with the same name that does something different could cause confusion. – Theodor Zoulias Nov 03 '20 at 18:11
  • 1
    Thanks for pointing that out Theodor; I changed the class name to avoid confusion. – Kees van Zon Nov 04 '20 at 20:02
0

A little late, but it might serve as a solution for someone...

I suggest creating a dictionary!

Dictionary<string, Task<MYTASK> dicMYTASKS;

In a cycle or by direct assignment, fill the dictionary with the desired names/references.

foreach (var REF in List<OBJECTXPTO>)
{
     dicMYTASKS.Add(REF, FunctionOfMYTASK());
}

Finally, when all the tasks are finished, you do a cycle to get the data (for example).

await Task.WhenAll(dicMYTASKS.Values);
foreach (var dicRES in dicMYTASKS)
{
     Console.WriteLine(dicRES.Key);
}

I hope it helps...

-2
public class NamedTaskSchedular
{
    private static readonly ConcurrentDictionary<string, NamesTask> NamedTaskDictionary = new ConcurrentDictionary<string, NamesTask>();

    public static Task RunNamedTask(string name, Action action)
    {
        if (NamedTaskDictionary.ContainsKey(name))
        {
            return NamedTaskDictionary[name].RunTask(action);
        }
        var task = new NamesTask();

        NamedTaskDictionary[name] = task;

        return task.RunTask(action);
    }
}
zx485
  • 28,498
  • 28
  • 50
  • 59