5

我正在开发一个向客户端公开 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%)。

有没有人有好的解决方案?

谢谢!

4

3 回答 3

5

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

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

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 请求的可能影响:

任务并行库如何在终端服务器或 Web 应用程序中扩展?

于 2010-08-03T15:50:14.010 回答
0

这可能会有所帮助:http: //blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html。除了报告进度之外,该解决方案还支持更新表单控件而不会出现跨线程操作无效异常

于 2013-10-25T06:02:15.907 回答
0

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

于 2010-08-03T14:19:56.570 回答