如何使用任务并行库执行多个任务,并在第一个实际返回数据后返回

时间:2012-03-18 05:25:21

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

我正在开发一个应用程序,其中尽可能快地将数据恢复到串行进程非常重要,但可以有多个源来从中获取数据。同样,有时一个源比另一个源更快,但你不知道将是哪个源。我正在使用ContinueWhenAny(...)。Wait()等待第一个Task结束,以便继续并返回调用方法。但是,我需要首先检查数据的有效性,然后才返回(或者如果所有任务都已完成且没有任何数据具有有效数据)。现在我的代码将返回甚至无效的数据,如果那是首先完成的任务。

有没有办法做类似“ContinueWhenAny”的事情,但只有当Task.Result满足某个条件时,否则等待下一个任务/等等......直到最后一个任务完成?

同样,我需要确保在一个结果有效后,其他线程取消。这部分工作正常。

目前,我的代码看起来像这样(剥离了异常处理,只是螺母和螺栓):

        ResultObject result = null;
        var tokenSource = new CancellationTokenSource();
        var tasks = listOfSources
                .Select(i => Task.Factory.StartNew(
                    () =>
                        {
                            i.CancellationToken = tokenSource.Token;
                            //Database Call
                            return i.getData(inputparameters);
                        }, tokenSource.Token));

        Task.Factory.ContinueWhenAny(
                tasks.ToArray(),
                firstCompleted =>
                    {
                        //This is the "result" I need to validate before setting and canceling the other threads
                        result = firstCompleted.Result;
                        tokenSource.Cancel();
                    }).Wait();
        return result;

有什么想法吗?我不想使用ContinueWhenAll,因为如果第一次调用需要2秒而第二次需要10秒,我想在第一次调用返回有效数据时在2秒内返回串行进程,否则等待10秒,希望结果具有有效数据,并且只有在所有任务都已完成并返回无效结果时才返回无效数据。

---------更新---- 感谢zmbq的好主意。更新的(工作)代码在下面并满足我的所有要求。但需要注意的是,此代码与前面的代码之间的区别在于,如果没有任何任务产生有效结果,则此代码将返回null结果,而不是返回无效结果本身的先前代码。改变这个版本也不难做到,但我完全满足于在我的目的中返回null。

        ResultObject result = null;
        var tokenSource = new CancellationTokenSource();
        var tasks = listOfSources
                .Select(i => Task.Factory.StartNew(
                    () =>
                        {
                            i.CancellationToken = tokenSource.Token;
                            //Database Call
                            return i.getData(inputparameters);
                        }, tokenSource.Token)).ToArray();

        result = GetFirstValidResult(tokenSource,tasks);

        return result;


   private ResultObject GetFirstValidResult(CancellationTokenSource tokenSource, Task<ResultObject>[] tasks)
    {
        ResultObject result = null;
        Task.Factory.ContinueWhenAny(
            tasks,
            firstCompleted =>
                {
                    var testResult = firstCompleted.Result;
                    if(testResult != null && testResult.IsValid())
                    {
                        result = testResult;
                        tokenSource.Cancel();
                    }
                    else
                    {
                        var remainingTasks = tasks.Except(new[]{firstCompleted}).ToArray();
                        if(remainingTasks.Any())
                        {
                            result = GetFirstValidResult(tokenSource, remainingTasks);
                        } 
                    }
                }).Wait();
        return result;
    }

1 个答案:

答案 0 :(得分:4)

好吧,如果你的第一个完成回调会检查结果,并且在结果是非法的情况下对其余任务调用ContinueWhenAny,那么你就完成了。

与往常一样,我建议你看看ZeroMQ。触发任务并让每个任务在结果合法时将消息写入输出队列。主线程将阻塞队列并在有效消息时返回,或者当所有任务都已完成时返回。