Memanggil beberapa layanan async secara paralel

17

Saya memiliki beberapa layanan REST async yang tidak saling bergantung. Itu sementara "menunggu" respons dari Service1, saya dapat memanggil Service2, Service3 dan sebagainya.

Misalnya, lihat kode di bawah ini:

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

// Use service1Response and service2Response

Sekarang, service2Responsetidak bergantung pada service1Responsedan mereka dapat diambil secara mandiri. Oleh karena itu, tidak perlu bagi saya untuk menunggu tanggapan dari layanan pertama untuk memanggil layanan kedua.

Saya tidak berpikir saya bisa menggunakan di Parallel.ForEachsini karena ini bukan operasi terikat CPU.

Untuk memanggil kedua operasi ini secara paralel, dapatkah saya memanggil penggunaan Task.WhenAll? Satu masalah yang saya lihat Task.WhenAlladalah tidak mengembalikan hasil. Untuk mengambil hasilnya, bisakah saya menelepon task.Resultsetelah menelepon Task.WhenAll, karena semua tugas sudah selesai dan semua yang saya butuhkan untuk menjemput kami?

Kode sampel:

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

await Task.WhenAll(task1, task2)

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

// Use result1 and result2

Apakah kode ini lebih baik daripada yang pertama dalam hal kinerja? Adakah pendekatan lain yang bisa saya gunakan?

Ankit Vijay
sumber
I do not think I can use Parallel.ForEach here since it is not CPU bound operation- Saya tidak melihat logika di sana. Konkurensi adalah konkurensi.
Robert Harvey
3
@RobertHarvey Saya menduga kekhawatirannya adalah bahwa, dalam konteks ini, Parallel.ForEachakan menelurkan utas baru sedangkan async awaitakan melakukan segalanya pada satu utas.
MetaFight
@Ankit tergantung pada kapan kode Anda perlu diblokir. Contoh kedua Anda akan diblokir sampai kedua respons siap. Contoh pertama Anda, mungkin, hanya akan secara logis memblokir ketika kode akan mencoba menggunakan respons ( await) sebelum siap.
MetaFight
Mungkin lebih mudah untuk memberi Anda jawaban yang lebih memuaskan jika Anda memberikan contoh yang kurang abstrak dari kode yang menggunakan kedua respons layanan.
MetaFight
@MetaFight Dalam contoh kedua saya, saya lakukan WhenAllsebelum saya lakukan Resultdengan gagasan bahwa ia menyelesaikan semua tugas sebelumnya. Hasilnya disebut. Karena, Task.Result memblokir utas panggilan, saya menganggap bahwa jika saya memanggilnya setelah tugas-tugas benar-benar selesai, ia akan segera mengembalikan hasil. Saya ingin memvalidasi pemahaman.
Ankit Vijay

Jawaban:

17

Satu masalah yang saya lihat menggunakan Task.WhenAll adalah bahwa itu tidak mengembalikan hasil

Tapi itu tidak mengembalikan hasil. Mereka semua akan berada dalam array dari tipe yang umum, jadi tidak selalu berguna untuk menggunakan hasil di mana Anda perlu menemukan item dalam array yang sesuai dengan Taskyang Anda inginkan hasilnya, dan berpotensi melemparkannya ke nya tipe aktual, jadi itu mungkin bukan pendekatan yang paling mudah / paling mudah dibaca dalam konteks ini, tetapi ketika Anda hanya ingin memiliki semua hasil dari setiap tugas, dan tipe umum adalah tipe yang ingin Anda perlakukan, maka itu bagus .

Untuk mengambil hasilnya, bisakah saya memanggil tugas. Menghasilkan setelah memanggil Tugas. Kapan, karena semua tugas sudah selesai dan semua yang saya butuhkan untuk menjemput kami?

Ya, Anda bisa melakukannya. Anda bisa juga awaitmereka ( awaitakan membuka pengecualian dalam tugas yang salah, sedangkan Resultakan membuang pengecualian agregat, tetapi kalau tidak akan sama).

Apakah kode ini lebih baik daripada yang pertama dalam hal kinerja?

Itu melakukan dua operasi pada saat yang sama, bukan satu dan kemudian yang lain. Apakah itu lebih baik atau lebih buruk tergantung pada apa operasi yang mendasarinya. Jika operasi yang mendasarinya adalah "membaca file dari disk" maka melakukannya secara paralel kemungkinan lebih lambat, karena hanya ada satu kepala disk dan itu hanya dapat berada di satu tempat pada waktu tertentu; itu melompat-lompat di antara dua file akan lebih lambat daripada membaca satu file lalu yang lainnya. Di sisi lain, jika operasi "melakukan beberapa permintaan jaringan" (seperti halnya di sini) maka mereka kemungkinan besar akan lebih cepat (setidaknya hingga sejumlah permintaan bersamaan), karena Anda dapat menunggu respons dari beberapa komputer jaringan lain sama cepatnya ketika ada juga beberapa permintaan jaringan yang tertunda sedang terjadi. Jika Anda ingin tahu apakah itu

Adakah pendekatan lain yang bisa saya gunakan?

Jika tidak penting bagi Anda bahwa Anda tahu semua pengecualian dilemparkan di antara semua operasi yang Anda lakukan secara paralel, bukan hanya yang pertama, Anda dapat dengan mudah awaitmelakukan tugas tanpa WhenAllsama sekali. Satu-satunya hal yang WhenAllmemberi Anda adalah memiliki AggregateExceptiondengan setiap pengecualian dari setiap tugas yang salah, daripada melempar ketika Anda menekan tugas yang salah pertama. Sesederhana:

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

var result1 = await task1;
var result2 = await task2;
Melayani
sumber
Itu tidak menjalankan tugas secara bersamaan apalagi secara paralel. Anda menunggu setiap tugas diselesaikan secara berurutan. Benar-benar baik jika Anda tidak peduli dengan kode performan.
Rick O'Shea
3
@ RickO'Shea Ini dimulai operasi berurutan. Ini akan memulai operasi kedua setelah itu * memulai operasi pertama. Tetapi memulai operasi asinkron pada dasarnya harus seketika (jika tidak, sebenarnya tidak asinkron, dan itu adalah bug dalam metode itu). Setelah memulai satu, lalu yang lain, itu tidak akan berlanjut sampai setelah yang pertama selesai, dan kemudian yang kedua selesai. Karena tidak ada yang menunggu yang pertama untuk menyelesaikan sebelum memulai yang kedua, tidak ada yang menghentikan mereka untuk berjalan bersamaan (yang sama dengan mereka berjalan secara paralel).
Servy
@Servy Saya tidak berpikir itu benar. Saya menambahkan login di dalam dua operasi async yang masing-masing mengambil sekitar satu detik (keduanya melakukan panggilan http) dan kemudian memanggil mereka seperti yang Anda sarankan, dan tentu saja cukup task1 mulai dan berakhir dan kemudian task2 mulai dan berakhir.
Matt Frear
@MattFrear Maka metode itu sebenarnya tidak sinkron. Itu sinkron. Menurut definisi , metode asinkron akan segera kembali, daripada kembali setelah operasi benar-benar selesai.
Servy
@Servy menurut definisi, penantian akan berarti Anda menunggu sampai tugas asinkron selesai sebelum menjalankan baris berikutnya. Bukan?
Matt Frear
0

Inilah metode ekstensi yang memanfaatkan SemaphoreSlim dan memungkinkan untuk mengatur tingkat paralelisme maksimum

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

Penggunaan sampel:

await enumerable.ForEachAsyncConcurrent(
    async item =>
    {
        await SomeAsyncMethod(item);
    },
    5);
Jay Shah
sumber
-2

Anda bisa menggunakan

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

atau

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();
pengguna1451111
sumber
Mengapa downvotes?
user1451111