【问题标题】:Tracking progress of a multi-step Task跟踪多步骤任务的进度
【发布时间】:2013-10-25 14:02:15
【问题描述】:

我正在开发一个向客户端公开 Web 服务的简单服务器。一些请求可能需要很长时间才能完成,并且在逻辑上分为多个步骤。对于此类请求,需要在执行过程中报告进度。此外,新请求可能会在前一个请求完成之前发起,并且要求两者同时执行(除非某些系统特定的限制)。

我正在考虑让服务器向其客户端返回一个 TaskId,并让客户端使用 TaskId 跟踪请求的进度。我认为这是一个很好的方法,剩下的问题是如何管理任务。

从未使用过 TPL,我认为这是解决此问题的好方法。事实上,它允许我同时运行多个任务,而无需手动管理线程。我什至可以使用 ContinueWith 相对轻松地创建多步骤任务。

不过,我想不出一个跟踪任务进度的好方法。我意识到,当我的请求包含一个“步骤”时,该步骤必须合作报告其状态。这是我现在更愿意避免的事情。但是,当请求包含多个步骤时,我想知道当前正在执行哪个步骤并相应地报告进度。我能想出的唯一方法是非常烦人:

Task<int> firstTask = new Task( () => { DoFirstStep(); return 3.14; } );
firstTask.
ContinueWith<int>( task => { UpdateProgress("50%"); return task.Result; } ).
ContinueWith<string>( task => { DoSecondStep(task.Result); return "blah"; }.
ContinueWith<string>( task => { UpdateProgress("100%"); return task.Result; } ).

即使这样也不完美,因为我希望任务存储自己的进度,而不是让 UpdateProgress 更新某个已知位置。此外,它还有一个明显的缺点,即在添加新步骤时必须更改很多地方(因为现在进度是 33%、66%、100% 而不是 50%、100%)。

谁有好的解决方案?

谢谢!

【问题讨论】:

    标签: c# c#-4.0 task-parallel-library


    【解决方案1】:

    这并不是任务并行库完全支持的场景。

    您可能会考虑一种方法,将进度更新提供给队列并在另一个任务上读取它们:

    static void Main(string[] args)
    {
        Example();
    }
    
    static BlockingCollection<Tuple<int, int, string>> _progressMessages = 
        new BlockingCollection<Tuple<int, int, string>>();
    
    public static void Example()
    {
        List<Task<int>> tasks = new List<Task<int>>();
    
        for (int i = 0; i < 10; i++)
            tasks.Add(Task.Factory.StartNew((object state) =>
                {
                    int id = (int)state;
                    DoFirstStep(id);
                    _progressMessages.Add(new Tuple<int, int, string>(
                        id, 1, "10.0%"));
                    DoSecondStep(id);
                    _progressMessages.Add(new Tuple<int, int, string>(
                        id, 2, "50.0%"));
    
                    // ...
    
                    return 1;
                },
                (object)i
                ));
    
        Task logger = Task.Factory.StartNew(() =>
            {
                foreach (var m in _progressMessages.GetConsumingEnumerable())
                    Console.WriteLine("Task {0}: Step {1}, progress {2}.",
                    m.Item1, m.Item2, m.Item3);
            });
    
    
        List<Task> waitOn = new List<Task>(tasks.ToArray());
        waitOn.Add(logger);
        Task.WaitAll(waitOn.ToArray());
        Console.ReadLine();
    }
    
    private static void DoSecondStep(int id)
    {
        Console.WriteLine("{0}: First step", id);
    }
    
    private static void DoFirstStep(int id)
    {
        Console.WriteLine("{0}: Second step", id);
    }
    

    此示例未显示取消、错误处理或说明您的任务可能长时间运行的要求。长时间运行的任务对调度程序提出了特殊要求。更多关于这方面的讨论可以在http://parallelpatterns.codeplex.com/ 找到,下载书稿并查看第 3 章。

    这只是在这样的场景中使用任务并行库的一种方法。 TPL 可能不是这里最好的方法。

    如果您的 Web 服务在 ASP.NET(或类似的 Web 应用程序服务器)中运行,那么您还应该考虑使用线程池中的线程来执行任务而不是服务 Web 请求的可能影响:

    How does Task Parallel Library scale on a terminal server or in a web application?

    【讨论】:

    • 感谢您的详细回复!我同意 TPL 可能不是这里最好的方法。我希望有机会使用它,但它可能要等待另一天......
    【解决方案2】:

    我认为您正在寻找的解决方案不会涉及 Task API。或者至少,不是直接的。它不支持完成百分比的概念,并且 Task/ContinueWith 函数需要参与该逻辑,因为它是仅在该级别可用的数据(只有 ContinueWith 的最终调用处于任何位置才能知道完成百分比,即便如此,在算法上这样做充其量只是一种猜测,因为它当然不知道一个任务是否会比另一个任务花费更长的时间.我建议您创建自己的 API 来执行此操作,可能利用 Task API做实际的工作。

    【讨论】:

    • 是的,我在发布问题后意识到,当使用 ContinueWith(...) 时,实际上没有一个包含所有其他任务的大任务,而是一个任务链,因此任何单个任务不知道前面是什么。感谢您的帮助!
    【解决方案3】:

    这可能会有所帮助:http://blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html。除了报告进度之外,此解决方案还可以在不获取 Cross-thread operation not valid exception 的情况下更新表单控件。

    【讨论】:

      猜你喜欢
      • 2013-02-14
      • 2016-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-28
      相关资源
      最近更新 更多