如何为 TPL 中的任务分配名称

Posted

技术标签:

【中文标题】如何为 TPL 中的任务分配名称【英文标题】:How can I assign a name to a task in TPL 【发布时间】:2012-11-25 14:01:06 【问题描述】:

我将使用在我的应用程序上运行的大量任务。由于某种原因,每组任务都在运行。我想给这些任务命名,这样当我看到并行任务窗口时,我可以很容易地识别它们。

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

如果有这样的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()。但不幸的是,并行任务窗口不使用 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;
    

【问题讨论】:

【参考方案1】:

您可以将任何对象与任何对象联系起来。这是任务的扩展。它使用 Wea​​kReference,因此当所有引用都超出范围时,任务仍然可以被垃圾回收。

用法:

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

扩展类:

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;
    

【讨论】:

@mehrandvd 我认为这应该是公认的答案:) 真的很喜欢这个,但是为什么不用ConcurrentDictionary呢? @Harry 我认为这取决于如何标记任务。这是在异步过程中完成的吗?如果是这种情况,那么确实需要使用锁或 ConcurrentDictionary。根据我的经验,默认情况下,任务不会在异步代码中创建(因此标记/命名)。 Tag() 返回 Task 而不是 void 也可能有意义 - 这样您可以链接调用。【参考方案2】:

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

令人惊讶的是,即使Parallel 也可以使用,即使Task 没有直接执行您的方法。我认为这是因为 Parallel Tasks 以某种方式从 Parallel 知道 Tasks 并以不同的方式处理它们。

【讨论】:

这很好,但在很多情况下,当您有多个任务执行相同的方法时,这无论如何都不是很有帮助 这是一个很好的解决方案,也是一个实用的解决方案。谢谢。但最好是创建名称。例如,A 人的计算,B 人的计算,......参数命名会更好......但我的基本问题是,为什么那里的任务没有简单的名称属性!【参考方案3】:

你不能命名任务。

任务库在内部使用线程池,因此无法命名线程。此外,您的继承方法也不起作用,因为像“.ContinueWith()”这样的方法将始终创建一个不会从您的类继承的新任务。

【讨论】:

目前我不需要 ContinueWith() 来继续我的 NamedTask 类型的任务。我只是希望我的任务有一个名字。我知道任务在内部使用线程,但是什么应该让我们不命名任务呢?有一个名字似乎是合乎逻辑的任务。 因为任务有名字是不合逻辑的。任务用于短的异步操作,而不是长请求。【参考方案4】:

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

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

【讨论】:

【参考方案5】:

我认为你不能命名这些任务。 您可以使用Task.Id 来跟踪任务。

【讨论】:

【参考方案6】:

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

【讨论】:

谢谢。不错的属性。它可能会有所帮助,我会检查它。但问题仍然存在。任务是不能有名字的东西吗?为什么? 我已经检查了属性。并行任务窗口不使用它。此外,我们可以覆盖 ToString() 而不是使用此属性。【参考方案7】:
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....");
                
            
        );
    

【讨论】:

使用以上两个类,如下 -NamedTaskSchedular.RunNamedTask("1", Program.Act1);公共静态无效 Act1()【参考方案8】:

我想有一本字典来帮助调试等。

这是我一直在做的一个示例:

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

【讨论】:

【参考方案9】:

感谢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;
        
    

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

【讨论】:

【参考方案10】:

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

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

这对我来说效果很好。

【讨论】:

已经有一个内置的TaskFactory 类。创建具有相同名称但执行不同操作的类可能会导致混淆。 感谢您指出西奥多;我更改了类名以避免混淆。【参考方案11】:
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);
    

【讨论】:

以上是关于如何为 TPL 中的任务分配名称的主要内容,如果未能解决你的问题,请参考以下文章

Project:如何为项目中的任务合理分配资源

如何为 ECS 任务分配 IAM 角色

Pig如何为列分配名称?

如何为R中向量的特定元素分配名称

如何为 size() 列分配名称?

如何为异步任务设置名称?