跟踪多步骤任务的进度

时间:2010-08-03 09:54:23

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

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

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

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

谢谢!

3 个答案:

答案 0 :(得分: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请求的可能影响:

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

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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