// let's say there is a list of 1000+ URLs
string[] urls = { "http://google.com", "http://yahoo.com", ... };
// now let's send HTTP requests to each of these URLs in parallel
urls.AsParallel().ForAll(async (url) => {
var client = new HttpClient();
var html = await client.GetStringAsync(url);
});
Inilah masalahnya, ini memulai 1000+ permintaan web secara bersamaan. Adakah cara mudah untuk membatasi jumlah permintaan http asinkron ini secara bersamaan? Sehingga tidak lebih dari 20 halaman web yang diunduh pada waktu tertentu. Bagaimana melakukannya dengan cara yang paling efisien?
c#
asynchronous
task-parallel-library
async-ctp
async-await
Coder Duka
sumber
sumber
HttpClient
ituIDisposable
, dan Anda harus membuangnya, terutama ketika Anda akan menggunakan 1000+ dari mereka.HttpClient
bisa digunakan sebagai tunggal untuk beberapa permintaan.Jawaban:
Anda pasti dapat melakukan ini di versi terbaru asinkron untuk .NET, menggunakan .NET 4.5 Beta. Posting sebelumnya dari 'usr' menunjuk ke artikel bagus yang ditulis oleh Stephen Toub, tetapi berita yang kurang diumumkan adalah async semaphore benar-benar berhasil masuk ke rilis Beta .NET 4.5
Jika Anda melihat
SemaphoreSlim
kelas kami yang kami cintai (yang seharusnya Anda gunakan karena lebih berkinerja daripada aslinyaSemaphore
), kelas ini sekarang menawarkanWaitAsync(...)
serangkaian kelebihan beban, dengan semua argumen yang diharapkan - interval waktu tunggu, token pembatalan, semua teman penjadwalan Anda yang biasa: )Stephen juga menulis entri blog yang lebih baru tentang barang .NET 4.5 baru yang keluar dengan beta, lihat Yang Baru untuk Paralelisme di .NET 4.5 Beta .
Terakhir, berikut ini beberapa contoh kode tentang cara menggunakan SemaphoreSlim untuk pelambatan metode asinkron:
Terakhir, tetapi mungkin yang layak disebutkan adalah solusi yang menggunakan penjadwalan berbasis TPL. Anda dapat membuat tugas terikat delegasi di TPL yang belum dimulai, dan mengizinkan penjadwal tugas kustom untuk membatasi konkurensi. Faktanya, ada contoh MSDN untuk itu di sini:
Lihat juga TaskScheduler .
sumber
HttpClient
Parallel.ForEach
bekerja dengan kode sinkron. Ini memungkinkan Anda memanggil kode asynchronous.IDisposable
dalamusing
atautry-finally
pernyataan, dan menjamin mereka miliki.Jika Anda memiliki IEnumerable (mis. String URL) dan Anda ingin melakukan operasi terikat I / O dengan masing-masing ini (mis. Membuat permintaan http asinkron) secara bersamaan DAN secara opsional Anda juga ingin menyetel jumlah maksimum konkuren Permintaan I / O secara real time, berikut cara melakukannya. Dengan cara ini Anda tidak menggunakan thread pool dkk, metode ini menggunakan semaphoreslim untuk mengontrol permintaan I / O bersamaan maksimum yang mirip dengan pola jendela geser satu permintaan selesai, meninggalkan semaphore dan yang berikutnya masuk.
penggunaan: await ForEachAsync (urlStrings, YourAsyncFunc, optionalMaxDegreeOfConcurrency);
sumber
using
akan bagus.Sayangnya, .NET Framework kehilangan kombinator terpenting untuk mengatur tugas-tugas asinkron paralel. Tidak ada hal bawaan seperti itu.
Lihatlah kelas AsyncSemaphore yang dibangun oleh Stephen Toub yang paling terhormat. Yang Anda inginkan disebut semafor, dan Anda memerlukan versi asinkronnya.
sumber
Ada banyak jebakan dan penggunaan langsung dari semaphore bisa menjadi rumit dalam kasus kesalahan, jadi saya akan menyarankan untuk menggunakan AsyncEnumerator NuGet Package alih-alih menemukan kembali roda:
sumber
Contoh Theo Yaung memang bagus, tapi ada varian tanpa daftar tugas menunggu.
sumber
ProccessUrl
atau subfungsinya akan benar-benar diabaikan. Mereka akan dimasukkan ke dalam Tasks, tetapi tidak diserap kembali ke penelepon asliCheck(...)
. Secara pribadi, itulah mengapa saya masih menggunakan Tasks dan fungsi kombinatornya sepertiWhenAll
danWhenAny
- untuk mendapatkan propagasi kesalahan yang lebih baik. :)SemaphoreSlim bisa sangat membantu di sini. Berikut metode ekstensi yang saya buat.
Contoh Penggunaan:
sumber
Pertanyaan lama, jawaban baru. @vitidev memiliki blok kode yang digunakan kembali hampir utuh dalam proyek yang saya ulas. Setelah berdiskusi dengan beberapa kolega, seseorang bertanya "Mengapa Anda tidak menggunakan metode TPL bawaan saja?" ActionBlock terlihat seperti pemenang di sana. https://msdn.microsoft.com/en-us/library/hh194773(v=vs.110).aspx . Mungkin tidak akan berakhir dengan mengubah kode yang ada tetapi pasti akan melihat untuk mengadopsi nuget ini dan menggunakan kembali praktik terbaik Mr. Softy untuk paralelisme throttled.
sumber
Berikut adalah solusi yang memanfaatkan sifat malas LINQ. Ini secara fungsional setara dengan jawaban yang diterima ), tetapi menggunakan pekerja-tugas daripada a
SemaphoreSlim
, dengan cara ini mengurangi jejak memori dari seluruh operasi. Pada awalnya mari kita membuatnya bekerja tanpa pembatasan. Langkah pertama adalah mengubah url kami menjadi tugas yang tak terhitung banyaknya.Langkah kedua adalah untuk
await
semua tugas secara bersamaan menggunakanTask.WhenAll
metode ini:Keluaran:
Implementasi Microsoft dari
Task.WhenAll
terwujud langsung yang disediakan enumerable ke array, menyebabkan semua tugas untuk mulai sekaligus. Kami tidak menginginkannya, karena kami ingin membatasi jumlah operasi asinkron serentak. Jadi kita perlu menerapkan alternatifWhenAll
yang akan menghitung jumlah kita dengan lembut dan perlahan. Kami akan melakukannya dengan membuat sejumlah tugas-pekerja (sama dengan tingkat konkurensi yang diinginkan), dan setiap tugas-pekerja akan menghitung satu tugas kami yang dapat dihitung dalam satu waktu, menggunakan kunci untuk memastikan bahwa setiap url-tugas akan diproses dengan hanya satu pekerja-tugas. Kemudian kamiawait
menyelesaikan semua pekerja-tugas, dan akhirnya kami mengembalikan hasilnya. Berikut implementasinya:... dan inilah yang harus kita ubah dalam kode awal kita, untuk mencapai pelambatan yang diinginkan:
Ada perbedaan terkait penanganan pengecualian. Native
Task.WhenAll
menunggu semua tugas selesai dan menggabungkan semua pengecualian. Implementasi di atas berakhir segera setelah menyelesaikan tugas yang salah pertama.sumber
IAsyncEnumerable<T>
dapat ditemukan di sini .Meskipun 1000 tugas mungkin diantrekan dengan sangat cepat, library Parallel Tasks hanya dapat menangani tugas bersamaan yang sama dengan jumlah inti CPU di mesin. Artinya, jika Anda memiliki mesin empat inti, hanya 4 tugas yang akan dijalankan pada waktu tertentu (kecuali Anda menurunkan MaxDegreeOfParallelism).
sumber
await
kata kunci di sana. Menghapus itu seharusnya menyelesaikan masalah, benar?Running
status) secara bersamaan daripada jumlah inti. Ini akan menjadi kasus khusus dengan Tugas terikat I / O.Perhitungan paralel harus digunakan untuk mempercepat operasi yang terikat dengan CPU. Di sini kita berbicara tentang operasi terikat I / O. Implementasi Anda harus murni asinkron , kecuali jika Anda membanjiri single core yang sibuk pada CPU multi-core Anda.
EDIT Saya suka saran yang dibuat oleh usr untuk menggunakan "semafor asinkron" di sini.
sumber
Gunakan
MaxDegreeOfParallelism
, yang merupakan opsi yang dapat Anda tentukan diParallel.ForEach()
:sumber
GetStringAsync(url)
dimaksudkan untuk dipanggil denganawait
. Jika Anda memeriksa jenisnyavar html
, itu adalahTask<string>
, bukan hasilnyastring
.Parallel.ForEach(...)
ditujukan untuk menjalankan blok kode sinkron secara paralel (misalnya pada utas yang berbeda).Pada dasarnya Anda akan ingin membuat Tindakan atau Tugas untuk setiap URL yang ingin Anda tekan, memasukkannya ke dalam Daftar, dan kemudian memproses daftar itu, membatasi jumlah yang dapat diproses secara paralel.
Entri blog saya menunjukkan cara melakukan ini dengan Tasks dan dengan Tindakan, dan memberikan contoh proyek yang dapat Anda unduh dan jalankan untuk melihat keduanya beraksi.
Dengan Actions
Jika menggunakan Actions, Anda dapat menggunakan fungsi .Net Parallel.Invoke bawaan. Di sini kami membatasinya untuk menjalankan maksimal 20 utas secara paralel.
Dengan Tasks
Dengan Tasks tidak ada fungsi bawaan. Namun, Anda dapat menggunakan yang saya sediakan di blog saya.
Dan kemudian membuat daftar Tugas Anda dan memanggil fungsi untuk menjalankannya, katakanlah maksimal 20 secara bersamaan, Anda dapat melakukan ini:
sumber
ini bukan praktik yang baik karena mengubah variabel global. ini juga bukan solusi umum untuk asinkron. tetapi mudah untuk semua contoh HttpClient, jika hanya itu yang Anda cari. Anda cukup mencoba:
sumber