Eşzamanlı eşzamansız G / Ç işlemlerinin miktarı nasıl sınırlandırılır?


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

Sorun şu ki, aynı anda 1000'den fazla web isteği başlatıyor. Bu eşzamansız http isteklerinin eşzamanlı miktarını sınırlamanın kolay bir yolu var mı? Böylece herhangi bir zamanda 20'den fazla web sayfası indirilmez. En verimli şekilde nasıl yapılır?


2
Bunun önceki sorunuzdan farkı nedir?
svick

1
stackoverflow.com/questions/9290498/… ParallelOptions parametresiyle.
Chris Disley

4
@ChrisDisley, bu sadece isteklerin başlatılmasına paralel olacaktır.
harcayan

@svick haklı, nasıl farklı? btw, cevabı seviyorum stackoverflow.com/a/10802883/66372
eglasius

3
Bunun yanında HttpClientolduğunu IDisposableve bunlardan 1000'in kullanacağız kullanmayın, özellikle imha edilmelidir. HttpClientbirden çok istek için tekli olarak kullanılabilir.
Shimmy Weitzhandler

Yanıtlar:


161

NET 4.5 Beta kullanarak .NET için async'in en son sürümlerinde bunu kesinlikle yapabilirsiniz. 'Usr'den önceki gönderi Stephen Toub tarafından yazılmış iyi bir makaleye işaret ediyor, ancak daha az duyurulan haber, asenkron semaforun aslında .NET 4.5'in Beta sürümüne girdiğidir.

Sevgili SemaphoreSlimsınıfımıza bakarsanız (orijinalinden daha performanslı olduğu için kullanmanız gerekir Semaphore), şimdiWaitAsync(...) beklenen tüm argümanlarla birlikte aşırı yükleme serisine - zaman aşımı aralıkları, iptal belirteçleri, tüm normal planlama arkadaşlarınız: )

Stephen ayrıca beta ile çıkan yeni .NET 4.5 güzellikleri hakkında daha yeni bir blog yazısı yazdı . .NET 4.5 Beta'da Paralellik için Yenilikler'e bakın. .

Son olarak, zaman uyumsuz yöntem azaltma için SemaphoreSlim'in nasıl kullanılacağıyla ilgili bazı örnek kodlar:

public async Task MyOuterMethod()
{
    // let's say there is a list of 1000+ URLs
    var urls = { "http://google.com", "http://yahoo.com", ... };

    // now let's send HTTP requests to each of these URLs in parallel
    var allTasks = new List<Task>();
    var throttler = new SemaphoreSlim(initialCount: 20);
    foreach (var url in urls)
    {
        // do an async wait until we can schedule again
        await throttler.WaitAsync();

        // using Task.Run(...) to run the lambda in its own parallel
        // flow on the threadpool
        allTasks.Add(
            Task.Run(async () =>
            {
                try
                {
                    var client = new HttpClient();
                    var html = await client.GetStringAsync(url);
                }
                finally
                {
                    throttler.Release();
                }
            }));
    }

    // won't get here until all urls have been put into tasks
    await Task.WhenAll(allTasks);

    // won't get here until all tasks have completed in some way
    // (either success or exception)
}

Son olarak, ancak muhtemelen bahsetmeye değer bir şey, TPL tabanlı çizelgeleme kullanan bir çözümdür. TPL'de henüz başlatılmamış temsilciye bağlı görevler oluşturabilir ve özel bir görev zamanlayıcının eşzamanlılığı sınırlamasına izin verebilirsiniz. Aslında, burada bunun için bir MSDN örneği var:

Ayrıca bkz . TaskScheduler .


3
paralellik değil, bu nedenle sınırlı derecede paralellik ile daha hoş bir yaklaşım değil mi? msdn.microsoft.com/en-us/library/…
GreyCloud

2
Neden seni elden HttpClient
atmıyorsun

4
@GreyCloud: Parallel.ForEachzaman uyumlu kodla çalışır. Bu, eşzamansız kodu çağırmanıza olanak tanır.
Josh Noe

2
@TheMonarch yanılıyorsun . O zaman tüm sarmak için iyi bir alışkanlıktır yanında IDisposableolduğundan da usingya try-finallytabloların ve bunların imha edilmesini güvence altına.
Shimmy Weitzhandler

29
Bu yanıtın ne kadar popüler olduğu göz önüne alındığında, HttpClient'in istek başına bir örnek yerine tek bir ortak örnek olabileceğine ve olması gerektiğine işaret etmeye değer.
Rupert Rawnsley

15

Bir IEnumerable (ör. URL dizeleri) varsa ve bunların her biri ile eşzamanlı olarak G / Ç bağlantılı bir işlem yapmak istiyorsanız (yani, zaman uyumsuz bir http isteği yapmak) VE isteğe bağlı olarak, aynı zamanda maksimum eşzamanlı sayısını da ayarlamak istiyorsanız Gerçek zamanlı I / O istekleri, işte bunu nasıl yapabileceğiniz. Bu şekilde, iş parçacığı havuzunu ve diğerlerini kullanmazsınız, yöntem, bir istek tamamlanır, semafordan ayrılır ve bir sonraki, kayan bir pencere düzenine benzer şekilde maksimum eşzamanlı G / Ç isteklerini kontrol etmek için semaforeslim kullanır.

kullanım: await ForEachAsync (urlStrings, YourAsyncFunc, optionalMaxDegreeOfConcurrency);

public static Task ForEachAsync<TIn>(
        IEnumerable<TIn> inputEnumerable,
        Func<TIn, Task> asyncProcessor,
        int? maxDegreeOfParallelism = null)
    {
        int maxAsyncThreadCount = maxDegreeOfParallelism ?? DefaultMaxDegreeOfParallelism;
        SemaphoreSlim throttler = new SemaphoreSlim(maxAsyncThreadCount, maxAsyncThreadCount);

        IEnumerable<Task> tasks = inputEnumerable.Select(async input =>
        {
            await throttler.WaitAsync().ConfigureAwait(false);
            try
            {
                await asyncProcessor(input).ConfigureAwait(false);
            }
            finally
            {
                throttler.Release();
            }
        });

        return Task.WhenAll(tasks);
    }


hayır, SemaphoreSlim'i bu uygulamada ve yöntemde dahili olarak kullanıldığından ve yöntem AvailableWaitHandle özelliğine erişmediğinden açıkça imha etmenize gerek yoktur, bu durumda onu bir kullanım bloğunun içine atmamız veya sarmamız gerekirdi.
Doğu Arslan

1
Sadece diğer insanlara öğrettiğimiz en iyi uygulamaları ve dersleri düşünüyorum. A usinggüzel olurdu.
AgentFire

Pekala, bu örneği takip edebilirim, ancak bunu yapmanın en iyi yolunun ne olduğunu bulmaya çalışıyorum, temelde bir azaltıcıya sahip olsam da, Func'im bir liste döndürecekti, bu da tamamlandığında tamamlanan tüm sonların son bir listesinde olmasını isteyebilir ... listede kilitli olması gerekiyor, önerileriniz var mı.
Seabizkit

yöntemi biraz güncelleyebilirsiniz, böylece gerçek görevlerin listesini döndürür ve arama kodunuzun içinden Task.WhenAll'ı beklersiniz. Task.WhenAll tamamlandığında, listedeki her görevi numaralandırabilir ve listesini son listeye ekleyebilirsiniz. Yöntem imzasını 'public static IEnumerable <Task <TOut>> ForEachAsync <TIn, TOut> (IEnumerable <TIn> inputEnumerable, Func <TIn, Task <TOut>> asyncProcessor, int? MaxDegreeOfParallelism = null)' olarak değiştirin
Doğu Arslan

7

Ne yazık ki .NET Framework, paralel eşzamansız görevleri düzenlemek için en önemli birleştiricileri kaçırıyor. Yerleşik böyle bir şey yoktur.

Bak AsyncSemaphore en saygın Stephen Toub tarafından inşa sınıfa. İstediğiniz şeye semafor denir ve bunun eşzamansız bir versiyonuna ihtiyacınız vardır.


12
"Ne yazık ki .NET Framework, paralel zaman uyumsuz görevleri düzenlemek için en önemli birleştiricileri kaçırıyor. Yerleşik böyle bir şey yok." .NET 4.5 Beta'dan itibaren artık doğru değil. SemaphoreSlim artık WaitAsync (...) işlevselliği sunuyor :)
Theo Yaung

SemaphoreSlim (yeni zaman uyumsuz yöntemleriyle) AsyncSemphore'a tercih edilmeli mi, yoksa Toub'un uygulamasının hala bazı avantajları var mı?
Todd Menier

Bence yerleşik tip, iyi test edilmiş ve iyi tasarlanmış olması muhtemel olduğu için tercih edilmelidir.
usr

4
Stephen, blog gönderisindeki bir soruya yanıt olarak, .NET 4.5 için SemaphoreSlim kullanmanın genellikle doğru yol olacağını doğrulayan bir yorum ekledi.
jdasilva

7

Bir çok tuzak vardır ve bir semaforun doğrudan kullanımı hata durumlarında yanıltıcı olabilir, bu nedenle tekerleği yeniden icat etmek yerine AsyncEnumerator NuGet Paketini kullanmanızı öneririm :

// 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
await urls.ParallelForEachAsync(async (url) => {
    var client = new HttpClient();
    var html = await client.GetStringAsync(url);
}, maxDegreeOfParalellism: 20);

4

Theo Yaung örneği güzel, ancak bekleyen görevler listesi olmayan bir varyant var.

 class SomeChecker
 {
    private const int ThreadCount=20;
    private CountdownEvent _countdownEvent;
    private SemaphoreSlim _throttler;

    public Task Check(IList<string> urls)
    {
        _countdownEvent = new CountdownEvent(urls.Count);
        _throttler = new SemaphoreSlim(ThreadCount); 

        return Task.Run( // prevent UI thread lock
            async  () =>{
                foreach (var url in urls)
                {
                    // do an async wait until we can schedule again
                    await _throttler.WaitAsync();
                    ProccessUrl(url); // NOT await
                }
                //instead of await Task.WhenAll(allTasks);
                _countdownEvent.Wait();
            });
    }

    private async Task ProccessUrl(string url)
    {
        try
        {
            var page = await new WebClient()
                       .DownloadStringTaskAsync(new Uri(url)); 
            ProccessResult(page);
        }
        finally
        {
            _throttler.Release();
            _countdownEvent.Signal();
        }
    }

    private void ProccessResult(string page){/*....*/}
}

4
Unutmayın, bu yaklaşımı kullanmanın bir tehlikesi vardır - ProccessUrlveya alt işlevlerinde meydana gelen istisnalar aslında göz ardı edilecektir. Görevler'e yakalanacaklar, ancak arayan asıl kişiye geri gönderilmeyecekler Check(...). Kişisel olarak, bu yüzden hala Görevler'i ve bunların birleştirici işlevlerini WhenAllve WhenAny- gibi daha iyi hata yayılımı elde etmek için kullanıyorum. :)
Theo Yaung

3

SemaphoreSlim burada çok yardımcı olabilir. İşte oluşturduğum uzatma yöntemi.

    /// <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="maxActionsToRunInParallel">Optional, max numbers of the actions to run in parallel,
    /// 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? maxActionsToRunInParallel = null)
    {
        if (maxActionsToRunInParallel.HasValue)
        {
            using (var semaphoreSlim = new SemaphoreSlim(
                maxActionsToRunInParallel.Value, maxActionsToRunInParallel.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 of the provided tasks to complete.
                await Task.WhenAll(tasksWithThrottler.ToArray());
            }
        }
        else
        {
            await Task.WhenAll(enumerable.Select(item => action(item)));
        }
    }

Örnek Kullanım:

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

0

Eski soru, yeni cevap. @vitidev, incelediğim bir projede neredeyse bozulmadan yeniden kullanılan bir kod bloğuna sahipti. Birkaç meslektaşla tartıştıktan sonra, "Neden yerleşik TPL yöntemlerini kullanmıyorsunuz?" ActionBlock, orada kazanan gibi görünüyor. https://msdn.microsoft.com/en-us/library/hh194773(v=vs.110).aspx . Muhtemelen mevcut herhangi bir kodu değiştirmeyecektir, ancak kesinlikle bu nuget'i benimsemeye ve Bay Softy'nin kısılmış paralellik için en iyi uygulamasını yeniden kullanmaya çalışacaktır.


0

İşte LINQ'nun tembel doğasından yararlanan bir çözüm. İşlevsel olarak kabul edilen cevaba eşdeğerdir ), ancak a yerine işçi görevlerini kullanır ve SemaphoreSlimbu şekilde tüm işlemin bellek ayak izini azaltır. İlk başta, onu yavaşlatmadan çalıştıralım. İlk adım, URL'lerimizi bir dizi göreve dönüştürmektir.

string[] urls =
{
    "https://stackoverflow.com",
    "https://superuser.com",
    "https://serverfault.com",
    "https://meta.stackexchange.com",
    // ...
};
var httpClient = new HttpClient();
var tasks = urls.Select(async (url) =>
{
    return (Url: url, Html: await httpClient.GetStringAsync(url));
});

İkinci adım, yöntemi awaitaynı anda kullanarak tüm görevleri yapmaktır Task.WhenAll:

var results = await Task.WhenAll(tasks);
foreach (var result in results)
{
    Console.WriteLine($"Url: {result.Url}, {result.Html.Length:#,0} chars");
}

Çıktı:

Url: https://stackoverflow.com , 105.574 karakter
Url: https://superuser.com , 126.953 karakter
Url: https://serverfault.com , 125.963 karakter
Url: https://meta.stackexchange.com , 185.276 karakter
...

Microsoft'un uygulama içinde Task.WhenAllgerçekleşmesi anında seferde başlatma tüm görevleri neden bir diziye enumerable sağladı. Bunu istemiyoruz çünkü eşzamanlı asenkron işlemlerin sayısını sınırlamak istiyoruz. Bu yüzden WhenAll, numaralandırılmamızı nazikçe ve yavaşça sıralayacak bir alternatif uygulamamız gerekecek . Bunu, bir dizi çalışan görevi oluşturarak (istenen eşzamanlılık düzeyine eşit) yapacağız ve her bir işçi görevi, her url görevinin işlenmesini sağlamak için bir kilit kullanarak bir seferde numaralandırılabilir bir görevi numaralandıracaktır. tek bir işçi görevi ile. Sonra awaittüm işçi görevlerinin tamamlanmasını istiyoruz ve sonunda sonuçları geri veriyoruz. İşte uygulama:

public static async Task<T[]> WhenAll<T>(IEnumerable<Task<T>> tasks,
    int concurrencyLevel)
{
    if (tasks is ICollection<Task<T>>) throw new ArgumentException(
        "The enumerable should not be materialized.", nameof(tasks));
    var locker = new object();
    var results = new List<T>();
    var failed = false;
    using (var enumerator = tasks.GetEnumerator())
    {
        var workerTasks = Enumerable.Range(0, concurrencyLevel)
        .Select(async _ =>
        {
            try
            {
                while (true)
                {
                    Task<T> task;
                    int index;
                    lock (locker)
                    {
                        if (failed) break;
                        if (!enumerator.MoveNext()) break;
                        task = enumerator.Current;
                        index = results.Count;
                        results.Add(default); // Reserve space in the list
                    }
                    var result = await task.ConfigureAwait(false);
                    lock (locker) results[index] = result;
                }
            }
            catch (Exception)
            {
                lock (locker) failed = true;
                throw;
            }
        }).ToArray();
        await Task.WhenAll(workerTasks).ConfigureAwait(false);
    }
    lock (locker) return results.ToArray();
}

... ve istenen kısıtlamayı elde etmek için ilk kodumuzda değiştirmemiz gereken şey:

var results = await WhenAll(tasks, concurrencyLevel: 2);

İstisnaların ele alınmasında bir fark vardır. Yerel Task.WhenAll, tüm görevlerin tamamlanmasını bekler ve tüm istisnaları toplar. Yukarıdaki uygulama, hatalı olan ilk görevin tamamlanmasının hemen ardından sona erer.


Bir döndüren AC # 8 uygulaması buradaIAsyncEnumerable<T> bulunabilir .
Theodor Zoulias

-1

1000 görev çok hızlı bir şekilde sıraya alınabilse de, Paralel Görevler kitaplığı yalnızca makinedeki CPU çekirdeği miktarına eşit eşzamanlı görevleri işleyebilir. Bu, dört çekirdekli bir makineniz varsa, belirli bir anda yalnızca 4 görevin yürütüleceği anlamına gelir (MaxDegreeOfParallelism'i düşürmediğiniz sürece).


8
Evet, ancak bu eşzamansız G / Ç işlemleriyle ilgili değil. Yukarıdaki kod, tek bir iş parçacığı üzerinde çalışıyor olsa bile aynı anda 1000'den fazla indirmeyi tetikleyecektir.
Grief Coder

awaitAnahtar kelimeyi orada görmedim . Bunu kaldırmak sorunu çözecektir, değil mi?
scottm

2
Kütüphane kesinlikle Runningçekirdek miktarından çok eşzamanlı çalışan ( durumla birlikte) daha fazla görevi yerine getirebilir . Bu, özellikle G / Ç'ye bağlı Görevler için geçerli olacaktır.
svick

@svick: evet. Maksimum eşzamanlı TPL görevini (iş parçacığı değil) verimli bir şekilde nasıl kontrol edeceğinizi biliyor musunuz?
Grief Coder

-1

CPU'ya bağlı işlemleri hızlandırmak için paralel hesaplamalar kullanılmalıdır. Burada G / Ç bağlı işlemlerden bahsediyoruz. Çok çekirdekli CPU'nuzdaki meşgul tek çekirdeği bunaltmadığınız sürece, uygulamanız tamamen eşzamansız olmalıdır .

EDIT Burada usr tarafından "asenkron semafor" kullanmak için yapılan öneriyi beğendim.


İyi bir nokta! Buradaki her görev eşzamansız ve eşitleme kodu içerecek olsa da (sayfa eşzamansız olarak indirildikten sonra eşzamanlı olarak işlenir). Kodun senkronizasyon kısmını CPU'lar arasında dağıtmaya ve aynı zamanda eşzamanlı asenkron G / Ç işlemlerinin miktarını sınırlamaya çalışıyorum.
Grief Coder

Neden? Çünkü aynı anda 1000'den fazla http isteğini başlatmak, kullanıcının ağ kapasitesine çok uygun bir görev olmayabilir.
harcayan

Paralel uzantılar, saf bir zaman uyumsuz çözümü manuel olarak uygulamak zorunda kalmadan G / Ç işlemlerini çoğullamanın bir yolu olarak da kullanılabilir. Buna katılıyorum, özensiz kabul edilebilir, ancak eşzamanlı işlemlerin sayısı için sıkı bir sınır tuttuğunuz sürece, muhtemelen iş parçacığı havuzunu çok fazla zorlamayacaktır.
Sean U

3
Bu cevabın bir cevap verdiğini sanmıyorum. Tamamen eşzamansız olmak burada yeterli değildir: Fiziksel IO'ları tıkanmayan bir şekilde gerçekten kısmak istiyoruz.
usr

1
Hmm .. emin değilim ... büyük bir proje üzerinde çalışırken, bir çok geliştirici bu görüşü kabul ederse, her geliştiricinin tek başına katkısı işleri uçurumun ötesine geçirmek için yeterli olmasa bile açlık çekersiniz. Yalnızca bir ThreadPool olduğu göz önüne alındığında, ona yarı saygılı davransanız bile ... herkes aynı şeyi yapıyorsa, sorun çıkabilir. Bu nedenle , ThreadPool'da uzun süre çalışmamayı her zaman tavsiye ederim .
harcayan

-1

Kullanım MaxDegreeOfParallelism, aşağıda belirtebileceğiniz bir seçenektir Parallel.ForEach():

var options = new ParallelOptions { MaxDegreeOfParallelism = 20 };

Parallel.ForEach(urls, options,
    url =>
        {
            var client = new HttpClient();
            var html = client.GetStringAsync(url);
            // do stuff with html
        });

4
Bunun işe yaradığını sanmıyorum. GetStringAsync(url)ile çağrılması amaçlanmıştır await. Eğer türünü incelemek durumunda var html, bu bir olduğunu Task<string>değil, sonuç string.
Neal Ehardt

2
@NealEhardt doğru. paralel Parallel.ForEach(...)olarak eşzamanlı kod bloklarını çalıştırmak için tasarlanmıştır (örneğin farklı iş parçacıkları üzerinde).
Theo Yaung

-1

Esasen, vurmak istediğiniz her URL için bir Eylem veya Görev oluşturmak, onları bir Listeye koymak ve ardından paralel olarak işlenebilecek sayıyı sınırlayarak bu listeyi işlemek isteyeceksiniz.

Blog gönderim , bunun hem Görevler hem de Eylemler ile nasıl yapılacağını gösterir ve her ikisini de çalışırken görmek için indirip çalıştırabileceğiniz örnek bir proje sağlar.

Eylemlerle

Eylemler kullanıyorsanız, yerleşik .Net Parallel.Invoke işlevini kullanabilirsiniz. Burada bunu paralel olarak en fazla 20 iş parçacığı ile sınırlandırıyoruz.

var listOfActions = new List<Action>();
foreach (var url in urls)
{
    var localUrl = url;
    // Note that we create the Task here, but do not start it.
    listOfTasks.Add(new Task(() => CallUrl(localUrl)));
}

var options = new ParallelOptions {MaxDegreeOfParallelism = 20};
Parallel.Invoke(options, listOfActions.ToArray());

Görevlerle

Görevler ile yerleşik bir işlev yoktur. Bununla birlikte, blogumda sağladığım birini kullanabilirsiniz.

    /// <summary>
    /// Starts the given tasks and waits for them to complete. This will run, at most, the specified number of tasks in parallel.
    /// <para>NOTE: If one of the given tasks has already been started, an exception will be thrown.</para>
    /// </summary>
    /// <param name="tasksToRun">The tasks to run.</param>
    /// <param name="maxTasksToRunInParallel">The maximum number of tasks to run in parallel.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    public static async Task StartAndWaitAllThrottledAsync(IEnumerable<Task> tasksToRun, int maxTasksToRunInParallel, CancellationToken cancellationToken = new CancellationToken())
    {
        await StartAndWaitAllThrottledAsync(tasksToRun, maxTasksToRunInParallel, -1, cancellationToken);
    }

    /// <summary>
    /// Starts the given tasks and waits for them to complete. This will run the specified number of tasks in parallel.
    /// <para>NOTE: If a timeout is reached before the Task completes, another Task may be started, potentially running more than the specified maximum allowed.</para>
    /// <para>NOTE: If one of the given tasks has already been started, an exception will be thrown.</para>
    /// </summary>
    /// <param name="tasksToRun">The tasks to run.</param>
    /// <param name="maxTasksToRunInParallel">The maximum number of tasks to run in parallel.</param>
    /// <param name="timeoutInMilliseconds">The maximum milliseconds we should allow the max tasks to run in parallel before allowing another task to start. Specify -1 to wait indefinitely.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    public static async Task StartAndWaitAllThrottledAsync(IEnumerable<Task> tasksToRun, int maxTasksToRunInParallel, int timeoutInMilliseconds, CancellationToken cancellationToken = new CancellationToken())
    {
        // Convert to a list of tasks so that we don't enumerate over it multiple times needlessly.
        var tasks = tasksToRun.ToList();

        using (var throttler = new SemaphoreSlim(maxTasksToRunInParallel))
        {
            var postTaskTasks = new List<Task>();

            // Have each task notify the throttler when it completes so that it decrements the number of tasks currently running.
            tasks.ForEach(t => postTaskTasks.Add(t.ContinueWith(tsk => throttler.Release())));

            // Start running each task.
            foreach (var task in tasks)
            {
                // Increment the number of tasks currently running and wait if too many are running.
                await throttler.WaitAsync(timeoutInMilliseconds, cancellationToken);

                cancellationToken.ThrowIfCancellationRequested();
                task.Start();
            }

            // Wait for all of the provided tasks to complete.
            // We wait on the list of "post" tasks instead of the original tasks, otherwise there is a potential race condition where the throttler's using block is exited before some Tasks have had their "post" action completed, which references the throttler, resulting in an exception due to accessing a disposed object.
            await Task.WhenAll(postTaskTasks.ToArray());
        }
    }

Ve sonra Görevler listenizi oluşturup, bir seferde en fazla 20 eşzamanlı olarak çalışacakları işlevi çağırarak, şunu yapabilirsiniz:

var listOfTasks = new List<Task>();
foreach (var url in urls)
{
    var localUrl = url;
    // Note that we create the Task here, but do not start it.
    listOfTasks.Add(new Task(async () => await CallUrl(localUrl)));
}
await Tasks.StartAndWaitAllThrottledAsync(listOfTasks, 20);

Sanırım SemaphoreSlim için initialCount'u belirtiyorsunuz ve SemaphoreSlim yapıcısında 2. parametre yani maxCount belirtmeniz gerekiyor.
Jay Shah

Her görevden gelen her yanıtın bir Listeye işlenmesini istiyorum. Sonuç veya yanıtı nasıl iade edebilirim
venkat

-1

küresel bir değişkeni değiştirdiği için bu iyi bir uygulama değildir. aynı zamanda asenkron için genel bir çözüm değildir. ancak peşinde olduğunuz tek şey buysa, tüm HttpClient örnekleri için kolaydır. deneyebilirsiniz:

System.Net.ServicePointManager.DefaultConnectionLimit = 20;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.