Gọi song song nhiều dịch vụ async


17

Tôi có một vài dịch vụ REST không đồng bộ không phụ thuộc lẫn nhau. Đó là trong khi "chờ" phản hồi từ Service1, tôi có thể gọi Service2, Service3, v.v.

Ví dụ, tham khảo mã dưới đây:

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

// Use service1Response and service2Response

Bây giờ, service2Responsekhông phụ thuộc vào service1Responsevà chúng có thể được tìm nạp độc lập. Do đó, tôi không cần phải chờ phản hồi của dịch vụ thứ nhất để gọi dịch vụ thứ hai.

Tôi không nghĩ rằng tôi có thể sử dụng Parallel.ForEachở đây vì nó không phải là hoạt động ràng buộc của CPU.

Để gọi hai thao tác này song song, tôi có thể gọi sử dụng Task.WhenAllkhông? Một vấn đề tôi thấy khi sử dụng Task.WhenAlllà nó không trả về kết quả. Để tìm nạp kết quả tôi có thể gọi task.Resultsau khi gọi Task.WhenAll, vì tất cả các tác vụ đã hoàn thành và tất cả những gì tôi cần để tìm phản hồi cho chúng tôi?

Mã mẫu:

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

await Task.WhenAll(task1, task2)

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

// Use result1 and result2

Mã này có tốt hơn mã đầu tiên về hiệu suất không? Bất kỳ phương pháp khác tôi có thể sử dụng?


I do not think I can use Parallel.ForEach here since it is not CPU bound operation- Tôi không thấy logic ở đó. Đồng thời là đồng thời.
Robert Harvey

3
@RobertHarvey Tôi đoán mối quan tâm là, trong bối cảnh này, Parallel.ForEachsẽ sinh ra các luồng mới trong khi async awaitsẽ làm mọi thứ trên một luồng.
MetaFight

@Ankit nó phụ thuộc một khi mã của bạn thích hợp để chặn. Ví dụ thứ hai của bạn sẽ chặn cho đến khi cả hai phản hồi đã sẵn sàng. Ví dụ đầu tiên của bạn, có lẽ sẽ chỉ chặn một cách hợp lý khi mã sẽ cố gắng sử dụng phản hồi ( await) trước khi nó sẵn sàng.
MetaFight

Có thể dễ dàng hơn để cung cấp cho bạn một câu trả lời thỏa mãn hơn nếu bạn cung cấp một ví dụ ít trừu tượng hơn về mã tiêu thụ cả hai phản hồi dịch vụ.
MetaFight

@MetaFight Trong ví dụ thứ hai của tôi, tôi đang làm WhenAlltrước khi thực hiện Resultvới ý tưởng rằng nó hoàn thành tất cả các nhiệm vụ trước .Result được gọi. Vì, Task.Result chặn luồng gọi, tôi cho rằng nếu tôi gọi nó sau khi các tác vụ thực sự hoàn thành thì nó sẽ trả về kết quả ngay lập tức. Tôi muốn xác nhận sự hiểu biết.
Ankit Vijay

Câu trả lời:


17

Một vấn đề tôi thấy khi sử dụng Task.When ALL là nó không trả về kết quả

Nhưng nó không trả lại kết quả. Tất cả chúng sẽ nằm trong một mảng của một loại phổ biến, vì vậy không phải lúc nào cũng hữu ích khi sử dụng kết quả mà bạn cần tìm mục trong mảng tương ứng với Taskkết quả mà bạn muốn và có khả năng đưa nó vào kết quả của nó loại thực tế, vì vậy nó có thể không phải là cách tiếp cận dễ nhất / dễ đọc nhất trong ngữ cảnh này, nhưng khi bạn chỉ muốn có tất cả các kết quả từ mọi tác vụ, và loại phổ biến là loại bạn muốn xử lý chúng, thì thật tuyệt .

Để tìm nạp kết quả, tôi có thể gọi task.Result sau khi gọi Task.When ALL, vì tất cả các nhiệm vụ đã hoàn thành và tất cả những gì tôi cần để tìm phản hồi cho chúng tôi?

Vâng, bạn có thể làm điều đó. Bạn cũng có thể sử dụng awaitchúng ( awaitsẽ hủy bỏ ngoại lệ trong bất kỳ tác vụ bị lỗi nào, trong khi đó Resultsẽ đưa ra một ngoại lệ tổng hợp, nhưng nếu không thì nó sẽ giống nhau).

Mã này có tốt hơn mã đầu tiên về hiệu suất không?

Nó thực hiện hai thao tác cùng một lúc, thay vì một và sau đó là hoạt động khác. Cho dù điều đó tốt hơn hay tồi tệ hơn đều phụ thuộc vào những hoạt động cơ bản đó là gì. Nếu các hoạt động cơ bản là "đọc một tệp từ đĩa" thì việc thực hiện chúng song song có thể chậm hơn, vì chỉ có một đầu đĩa và nó chỉ có thể ở một nơi tại bất kỳ thời điểm nào; nó nhảy xung quanh giữa hai tệp sẽ chậm hơn đọc một tệp này đến tệp khác. Mặt khác, nếu các hoạt động "thực hiện một số yêu cầu mạng" (như trường hợp ở đây) thì rất có thể chúng sẽ nhanh hơn (ít nhất là lên đến một số yêu cầu đồng thời nhất định), vì bạn có thể chờ phản hồi từ một số máy tính mạng khác cũng nhanh như vậy khi có một số yêu cầu mạng đang chờ xử lý khác đang diễn ra. Nếu bạn muốn biết nếu nó '

Bất kỳ phương pháp khác tôi có thể sử dụng?

Nếu điều đó không quan trọng với bạn rằng bạn biết tất cả các ngoại lệ được đưa ra trong số tất cả các hoạt động bạn đang thực hiện song song thay vì chỉ hoạt động đầu tiên, bạn hoàn toàn có thể thực hiện awaitcác nhiệm vụ mà không cần WhenAll. Điều duy nhất WhenAllmang lại cho bạn là có một AggregateExceptionngoại lệ duy nhất từ ​​mọi tác vụ bị lỗi, thay vì ném khi bạn thực hiện tác vụ bị lỗi đầu tiên. Nó đơn giản như:

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

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

Đó không phải là chạy các nhiệm vụ đồng thời để một mình song song. Bạn đang chờ đợi từng nhiệm vụ hoàn thành theo thứ tự. Hoàn toàn tốt nếu bạn không quan tâm đến mã hiệu suất.
Rick O'Shea

3
@ RickO'Shea Nó bắt đầu các hoạt động tuần tự. Nó sẽ bắt đầu hoạt động thứ hai sau khi nó * bắt đầu hoạt động đầu tiên. Nhưng về cơ bản , bắt đầu hoạt động không đồng bộ nên về cơ bản là tức thời (nếu không, nó không thực sự không đồng bộ và đó là một lỗi trong phương thức đó). Sau khi bắt đầu một, và sau đó, nó sẽ không tiếp tục cho đến khi kết thúc đầu tiên, và sau đó kết thúc thứ hai. Vì không có gì chờ đợi cái đầu tiên kết thúc trước khi bắt đầu cái thứ hai, nên không có gì ngăn chúng chạy đồng thời (giống như chúng chạy song song).
Phục vụ

@Servy Tôi không nghĩ đó là sự thật. Tôi đã thêm đăng nhập bên trong hai thao tác không đồng bộ, mỗi lần mất khoảng một giây (cả hai thực hiện cuộc gọi http) và sau đó gọi chúng như bạn đã đề xuất, và chắc chắn đủ task1 bắt đầu và kết thúc rồi task2 bắt đầu và kết thúc.
Matt Frear

@MattFrear Sau đó, phương thức này không thực sự không đồng bộ. Đó là sự đồng bộ. Theo định nghĩa , một phương thức không đồng bộ sẽ quay trở lại ngay lập tức, thay vì quay trở lại sau khi hoạt động thực sự kết thúc.
Phục vụ

@Servy theo định nghĩa, việc chờ đợi sẽ có nghĩa là bạn đợi cho đến khi tác vụ không đồng bộ kết thúc trước khi thực hiện dòng tiếp theo. Phải không?
Matt Frear

0

Đây là phương thức mở rộng sử dụng SemaphoreSlim và cho phép đặt mức độ song song tối đa

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

Sử dụng mẫu:

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

-2

Bạn có thể sử dụng

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

hoặc là

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

Tại sao downvote?
dùng1451111
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.