C# 在不同时间需要返回值时将任务链接在一起的正确方法

C# 在不同时间需要返回值时将任务链接在一起的正确方法,c#,concurrency,task-parallel-library,task,C#,Concurrency,Task Parallel Library,Task,我希望这是有意义的——假设我有以下代码: Task.Run(() => { return Task.WhenAll ( Task1, Task2, ... Taskn ) .ContinueWith(tsks=> { TaskA (uses output from Tasks T

我希望这是有意义的——假设我有以下代码:

Task.Run(() =>
{
    return Task.WhenAll
        (
            Task1,
            Task2,
            ...
            Taskn
        )
        .ContinueWith(tsks=>
            {
                TaskA (uses output from Tasks Task1 & Task2, say)
            }
        , ct)
        .ContinueWith(res =>
            {
                TaskB (uses output from TaskA and Task3, say)
            }
        , ct);
});
因此,我希望我的前N个任务都同时运行(因为我们没有相互依赖关系),然后只有在它们全部完成后,才能继续执行依赖于它们的输出的任务(为此,我可以使用
tsks.Result
)。 但接下来我想继续执行一项任务,该任务依赖于第一个任务之一和
TaskA
的结果

我有点不知道如何正确地构造代码,以便在立即进行的
ContinueWith
之外访问第一组任务的结果

我的一个想法是在我的方法中为它们分配返回值-类似于:

... declare variables outside of Tasks ...

Task.Run(() =>
{
    return Task.WhenAll
        (
            Task.Run(() => { var1 = Task1.Result; }, ct),
            ...
            Task.Run(() => { varn = Taskn.Result; }, ct),
        )
        .ContinueWith(tsks=>
            {
                TaskA (uses output from Tasks var1 & varn, say)
            }
        , ct)
        .ContinueWith(res =>
            {
                TaskB (uses output from TaskA and var3, say)
            }
        , ct);
});
但是,即使这对我来说是可行的,我确信这是错误的

正确的方法是什么?我应该有一个包含所有必要变量的状态对象,并在我的所有任务中传递它吗?总的来说有更好的方法吗


请原谅我的无知-我只是对并发编程非常陌生。

因为
Task1
Task2
<代码>任务n在调用
whalll
的范围内,并且由于当
ContinueWith
将控制权传递给下一个任务时,所有先前的任务都保证完成,因此在代码中使用
任务x是安全的。结果
在实现continuations的代码中:

.ContinueWith(tsks=>
        {
            var resTask1 = Task1.Result;
            ...
        }
    , ct)

由于任务
Task1
已完成运行,因此可以保证在不阻塞的情况下获得结果。

这里有一种使用ConcurrentDictionary的方法,听起来它可能适用于您的用例。此外,由于您是并发新手,它还向您显示了联锁类:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Executing...");

        var numOfTasks = 50;
        var tasks = new List<Task>();
        for (int i = 0; i < numOfTasks; i++)
        {
            var iTask = Task.Run(() =>
            {
                var counter = Interlocked.Increment(ref _Counter);

                Console.WriteLine(counter);

                if (counter == numOfTasks - 1)
                {
                    Console.WriteLine("Waiting {0} ms", 5000);
                    Task.Delay(5000).Wait(); // to simulate a longish running task
                }

                _State.AddOrUpdate(counter, "Updated Yo!", (k, v) =>
                {
                    throw new InvalidOperationException("This shouldn't occure more than once.");
                });
            });
            tasks.Add(iTask);
        }

        Task.WhenAll(tasks)
            .ContinueWith(t =>
            {
                var longishState = _State[numOfTasks - 1];
                Console.WriteLine(longishState);
                Console.WriteLine("Complete. longishState: " + longishState);
            });

        Console.ReadKey();
    }

    static int _Counter = -1;
    static ConcurrentDictionary<int, string> _State = new ConcurrentDictionary<int, string>();
}
类程序
{
静态void Main(字符串[]参数)
{
Console.WriteLine(“正在执行…”);
var-numotasks=50;
var tasks=新列表();
for(int i=0;i
{
var计数器=联锁增量(参考计数器);
控制台。写线(计数器);
if(计数器==numotasks-1)
{
Console.WriteLine(“等待{0}ms”,5000);
Task.Delay(5000.Wait();//用于模拟长时间运行的任务
}
_State.AddOrUpdate(计数器,“Updated Yo!”,(k,v)=>
{
抛出新的InvalidOperationException(“这不应该发生多次。”);
});
});
任务。添加(iTask);
}
Task.WhenAll(任务)
.ContinueWith(t=>
{
var longishState=_State[numOfTasks-1];
控制台写入线(longishState);
Console.WriteLine(“Complete.longishState:+longishState”);
});
Console.ReadKey();
}
静态int_计数器=-1;
静态ConcurrentDictionary_State=新ConcurrentDictionary();
}
您得到的输出与此类似(尽管在继续之前等待队列并不总是最后一个):
解决这个问题的一个优雅方法是使用

像这样:

var nrOfTasks = ... ;
ConcurrentDictionary<int, ResultType> Results = new ConcurrentDictionary<int, ResultType>();

var barrier = new Barrier(nrOfTasks, (b) =>
{
    // here goes the work of TaskA
    // and immediatley
    // here goes the work of TaskB, having the results of TaskA and any other task you might need
});

Task.Run(() => { Results[1] = Task1.Result; barrier.SignalAndWait(); }, ct),
...
Task.Run(() => { Results[nrOfTasks] = Taskn.Result; barrier.SignalAndWait(); }, ct
var nroftask=;
ConcurrentDictionary结果=新建ConcurrentDictionary();
var屏障=新屏障(nRFtask,(b)=>
{
//这是塔斯卡的工作
//马上
//这里是TaskB的工作,有TaskA的结果以及您可能需要的任何其他任务
});
Task.Run(()=>{Results[1]=Task1.Result;barrier.SignalAndWait();},ct),
...
Task.Run(()=>{Results[nrofttask]=Taskn.Result;barrier.SignalAndWait();},ct

非常感谢您的回复,但是,无论出于何种原因,我无法访问
Task1.Result
。我的任务设置如下
var Task1=Task.Run(()=>…)
这可能是错误的吗???@JohnBustos当你使用
任务时。运行
,你得到的任务
没有结果。要得到有结果的任务,请使用
var Task1=Task.Factory.StartNew(()=>…返回类型的expr…);
@dasblinkenlight Task.Run支持返回值。可能您不知道。异步编程的高级组织方式有多种,例如Rx、TPL。但听起来您可以使用Parallel.For/Foreach/etc。您知道它的存在吗?请参阅底部的备注了解如何使用:您应该能够使用在这里等着,这简化了很多。