并行调用多个异步服务


17

我很少有彼此不依赖的异步REST服务。那就是在“等待” Service1的响应时,我可以调用Service2,Service3等。

例如,参考下面的代码:

var service1Response = await HttpService1Async();
var service2Response = await HttpService2Async();

// Use service1Response and service2Response

现在,它们service2Response不再依赖service1Response并且可以独立获取。因此,不需要我等待第一服务的响应来呼叫第二服务。

我不认为我可以Parallel.ForEach在这里使用,因为它不是CPU限制的操作。

为了并行调用这两个操作,可以调用use Task.WhenAll吗?我看到的一个问题Task.WhenAll是它不会返回结果。要获取结果,我可以在致电task.Result后调用Task.WhenAll,因为所有任务都已经完成并且我需要获取我们所有的响应?

样例代码:

var task1 = HttpService1Async();
var task2 = HttpService2Async();

await Task.WhenAll(task1, task2)

var result1 = task1.Result;
var result2 = task2.Result;

// Use result1 and result2

就性能而言,此代码是否比第一个更好?我可以使用其他方法吗?


I do not think I can use Parallel.ForEach here since it is not CPU bound operation-我看不到那里的逻辑。并发就是并发。
罗伯特·哈维

3
@RobertHarvey我猜担心的是,在这种情况下,Parallel.ForEach将生成新线程,而async await在单个线程上执行所有操作。
MetaFight

@Ankit取决于何时适合阻塞代码。您的第二个示例将阻塞,直到两个响应都准备好为止。假设您的第一个示例仅在逻辑上阻止代码await在准备就绪之前尝试使用响应()的时间。
MetaFight

如果您提供了使用两个服务响应的代码的抽象程度较低的示例,则可能更容易为您提供更令人满意的答案。
MetaFight

@MetaFight在我的第二个示例中,我正在做的事情是WhenAll在调用.Result 之前Result完成所有任务。由于Task.Result阻止了调用线程,因此我假设如果在任务实际完成后调用它,它将立即返回结果。我想验证理解。
Ankit Vijay

Answers:


17

我看到使用Task.WhenAll的一个问题是它不返回结果

但是它确实返回结果。它们都将位于通用类型的数组中,因此使用结果并不总是有用,因为您需要在数组中找到与Task您想要的结果相对应的项目,并可能将其转换为实际类型,因此在这种情况下可能不是最简单/最易读的方法,但是当您只想获取每个任务的所有结果,而通用类型是您想将它们视为的类型时,那很好。

为了获取结果,我可以在调用Task.WhenAll之后调用task.Result,因为所有任务都已经完成并且我需要获取我们的响应?

是的,您可以这样做。您也可以使用await它们(await将在任何有故障的任务中解开异常,而Result将引发聚合异常,但否则它将是相同的)。

就性能而言,此代码是否比第一个更好?

它同时执行两个操作,而不是一个然后执行另一个。是好还是坏取决于这些基础操作是什么。如果基础操作是“从磁盘读取文件”,则并行进行操作可能会较慢,因为只有一个磁盘头,并且在任何给定时间只能位于一个位置;它在两个文件之间跳转比读取一个文件再读取另一个文件要慢。另一方面,如果操作是“执行某些网络请求”(如此处所示),则它们很有可能会更快(至少达到一定数量的并发请求),因为您可以等待响应从其他网络计算机访问其他网络计算机的速度也一样快。如果你想知道是否

我可以使用其他方法吗?

如果这对你并不重要,你知道所有在所有你做并行而不只是第一个操作的抛出的异常,你可以简单await的任务,而无需WhenAll在所有。唯一WhenAll给您的是AggregateException每个故障任务都有一个异常,而不是在您遇到第一个故障任务时就抛出异常。就像这样简单:

var task1 = HttpService1Async();
var task2 = HttpService2Async();

var result1 = await task1;
var result2 = await task2;

那不是同时运行任务,更不用说并行运行了。您正在等待每个任务按顺序完成。如果您不关心性能代码,则完全可以。
里克·奥谢

3
@ RickO'Shea 顺序启动操作。这将是*启动后,启动第二操作第一操作。但是启动异步操作应该基本上是瞬时的(如果不是,那么它实际上不是异步的,那是该方法中的错误)。先启动一个,然后启动另一个,直到第一个完成然后第二个完成之后,它才能继续。由于没有什么等待第一个完成之前开始等待,因此没有什么阻止它们同时运行(这与它们并行运行相同)。
Servy

@Servy我认为那不是真的。我在两个异步操作中添加了日志记录,每个异步操作大约花费一秒钟(都进行http调用),然后按照您的建议进行调用,并确保足够的task1开始和结束,然后task2开始和结束。
马特·弗雷尔

@MattFrear然后该方法实际上不是异步的。这是同步的。 根据定义,异步方法将立即返回,而不是在操作实际完成之后返回。
Servy

@Servy根据定义,等待将意味着您等待异步任务完成后再执行下一行。是不是
马特·弗雷尔

0

这是利用SemaphoreSlim并允许设置最大并行度的扩展方法

    /// <summary>
    /// Concurrently Executes async actions for each item of <see cref="IEnumerable<typeparamref name="T"/>
    /// </summary>
    /// <typeparam name="T">Type of IEnumerable</typeparam>
    /// <param name="enumerable">instance of <see cref="IEnumerable<typeparamref name="T"/>"/></param>
    /// <param name="action">an async <see cref="Action" /> to execute</param>
    /// <param name="maxDegreeOfParallelism">Optional, An integer that represents the maximum degree of parallelism,
    /// Must be grater than 0</param>
    /// <returns>A Task representing an async operation</returns>
    /// <exception cref="ArgumentOutOfRangeException">If the maxActionsToRunInParallel is less than 1</exception>
    public static async Task ForEachAsyncConcurrent<T>(
        this IEnumerable<T> enumerable,
        Func<T, Task> action,
        int? maxDegreeOfParallelism = null)
    {
        if (maxDegreeOfParallelism.HasValue)
        {
            using (var semaphoreSlim = new SemaphoreSlim(
                maxDegreeOfParallelism.Value, maxDegreeOfParallelism.Value))
            {
                var tasksWithThrottler = new List<Task>();

                foreach (var item in enumerable)
                {
                    // Increment the number of currently running tasks and wait if they are more than limit.
                    await semaphoreSlim.WaitAsync();

                    tasksWithThrottler.Add(Task.Run(async () =>
                    {
                        await action(item).ContinueWith(res =>
                        {
                            // action is completed, so decrement the number of currently running tasks
                            semaphoreSlim.Release();
                        });
                    }));
                }

                // Wait for all tasks to complete.
                await Task.WhenAll(tasksWithThrottler.ToArray());
            }
        }
        else
        {
            await Task.WhenAll(enumerable.Select(item => action(item)));
        }
    }

用法示例:

await enumerable.ForEachAsyncConcurrent(
    async item =>
    {
        await SomeAsyncMethod(item);
    },
    5);

-2

您可以使用

Parallel.Invoke(() =>
{
    HttpService1Async();
},
() =>
{   
    HttpService2Async();
});

要么

Task task1 = Task.Run(() => HttpService1Async());
Task task2 = Task.Run(() => HttpService2Async());

//If you wish, you can wait for a particular task to return here like this:
task1.Wait();

为什么要投票?
user1451111 18-10-25
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.