正确的方法时,在不同的时间,需要返回值联系在一起的任务(Correct way to link Ta

2019-10-24 08:40发布

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

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

不过,尽管这对我的作品,我毫不怀疑 ,是做错了。

什么是正确的方法是什么? 如果我有一个包含所有必需的变量,并通过在整个我的所有任务的状态对象? 有没有在总更好的办法?

请在此处原谅我的无知 - 我只是很新的并发编程。

Answer 1:

由于Task1Task2 ,..., TaskN在范围上进行的呼叫WhenAll ,同时,由于时间ContinueWith将控制传递给下一个任务所有早期任务,保证完成,它是安全的使用TaskX.Result内代码执行的延续:

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

你肯定可以得到的结果不会阻塞,因为任务Task1完成运行。



Answer 2:

这里是一个办法与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>();
}

你得到类似的输出连接到这个(虽然它的等待线不会永远延续前的最后一次):



Answer 3:

解决这个优雅的方式是使用屏障类 。

像这样:

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


文章来源: Correct way to link Tasks together when return values are needed at different times