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

时间:2015-07-30 17:30:37

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

我希望这是有道理的 - 假设我有以下代码:

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);
});

但是,即使这对我有用,我也毫无疑问这样做错了。

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

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

3 个答案:

答案 0 :(得分:2)

由于Task1Task2,...,TaskN适用于WhenAll的调用,并且因为ContinueWith通过控制的时间对于您的下一个任务,所有早期任务都可以保证完成,在实现continuation的代码中使用TaskX.Result是安全的:

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

您可以保证在不阻止的情况下获得结果,因为任务Task1已经完成运行。

答案 1 :(得分:0)

这是一种使用ConcurrentDictionary的方法,听起来它可能适用于您的用例。此外,由于您不熟悉并发性,因此它也会向您显示Interlocked类:

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>();
}

你得到类似于此的输出(尽管等待行并不总是在继续之前的最后一行): enter image description here

答案 2 :(得分:0)

解决此问题的一种优雅方法是使用Barrier class

像这样:

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