如何在TPL中为任务指定名称

时间:2012-12-07 11:34:54

标签: c# task-parallel-library parallel.foreach

我将在我的应用程序上运行许多任务。由于某种原因,每组任务都在运行。我想命名这些任务,所以当我观察并行任务窗口时,我可以轻松识别它们。

从另一个角度来看,考虑我在框架级别使用任务来填充列表。使用我的框架的开发人员也在为她的工作使用任务。如果她查看并行任务窗口,她会发现一些不知道的任务。我想命名任务,以便将框架任务与她的任务区分开来。

如果有这样的API会非常方便:

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

或者也许:

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

或甚至在使用Parallel.ForEach

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

是否可以命名任务?

是否可以为Parallel.ForEach提供命名结构(可能使用lambda),以便创建具有该命名的任务?

我缺少哪个API?


我还尝试使用继承的任务来覆盖它的ToString()。但不幸的是,Parallel Tasks窗口不使用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;
    }
}

12 个答案:

答案 0 :(得分:23)

您可以将任何对象与任何对象相关联。这是Task的扩展。它使用WeakReference,因此当所有引用都超出范围时,任务仍然可以被垃圾收集。

用法:

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;
    }
}

扩展类:

<%= form_for @user, url: { action: "assign" } do |f| %>
  <%= f.label :follower_id, "Assign #{@user.name} to" %>
  <%= f.collection_select :following, @ders, :id, :name, prompt: true %>
  <%= f.submit "Assign", class: "btn btn-primary" %>
<% end %>

答案 1 :(得分:6)

您无法真正命名Task,但您可以命名由Task执行的方法,然后在并行任务窗口中显示该方法。因此,如果命名Task对您很重要,请不要使用lambdas,使用常规命名方法。

令人惊讶的是,即使Parallel没有直接执行您的方法,这也适用于Task。我认为这是因为并行任务以某种方式知道来自Task的{​​{1}}并以不同的方式处理它们。

答案 2 :(得分:2)

您无法命名任务。

任务库内部使用线程池,因此无法命名线程。你的继承方法也行不通,因为像“.ContinueWith()”这样的方法总会创建一个新的任务,它不会从你的类继承。

答案 3 :(得分:2)

如果您只需要在任务完成后知道任务的名称,那么您可以将其作为参数传递。将其作为任务结果的一部分返回。

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

或将您的任务映射到字典

        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}");
        }

答案 4 :(得分:1)

我认为你不能说出任务的名称。 您可以使用Task.Id来跟踪任务。

答案 5 :(得分:1)

我在这里盲目,因为我不知道并行任务窗口的行为,但是如果它使用调试器api在NamedTask子类上添加DebuggerDisplay属性可能有帮助

答案 6 :(得分: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....");
                }
            }
        });
    }
}

答案 7 :(得分:0)

我想到了一个字典来帮助调试等。

这是我一直在做的事的一个例子:

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

答案 8 :(得分:0)

感谢mike's answer我最终得到了

    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;
        }
    }

现在它是线程安全的,它还支持一个名称,而不仅仅是一个标签。

答案 9 :(得分:0)

如果这对任何人都有帮助,我将解决以下问题:

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);
    }
}

用法:

// 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);

这对我来说很好。

答案 10 :(得分:-1)

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);
    }
}

答案 11 :(得分:-1)

您可以在线程内命名线程。

Task.Factory.StartNew(MyTask);

public void MyTask(){
            Thread.CurrentThread.Name = "This is my thread";
}