2015-03-25 8 views
8

C# 'yi HttpClient ile eşzamansız öğrenmeye çalışıyorum ve neler olduğunu anlayamadığım bir sorunla karşılaştım.Birden çok zaman uyumsuz görevi bir satırda çağırırken (HttpClient kullanarak) Async kodu kısmen engelleniyor gibi görünüyor.

  • tek seferde HttpClient birden çok istek gönderme ve yanıtları gelir gelmez iade olarak ayrı ayrı işleme koyduk: Yapmak istediğim ne

    . Bu işlem sonsuza kadar bir döngüde tekrarlanır (yani, her birkaç saniyede bir yeni talep istemi). Sonuçlarda gerçekleştirilen işlem CPU yoğun değildir ve yanıt başına en fazla birkaç milisaniye sürer.

  • Tek bir durumda 5-50 gönderi var
  • Geri gelme sırasına aldırış etmeyin, ancak her istek/yanıt engellemeden mümkün olduğunca hızlı işlem görmelidir.

sorun: Birlikte istekleri içeren bir listenin yollanır ise

  • , tepkiler hiç bu kadar biraz daha uzun sürecek gibi görünüyor, ancak her biri yaklaşık aynı olmalıdır. örneğin:

    Zaman alınan:
    Zaman alınan 67,9609 milisaniye:
    Zaman alınan 89,2413 milisaniye: 100,2345 milisaniye
    Zaman alınan: 117,7308 milisaniye
    Zaman alınan: 127,6843 milisaniye
    Zaman alınan: 132,3066 milisaniye
    Zaman alınan : 157.3057 milisaniye

gerektiği tüm sadece 70ms civarında ... Bu her yeni parti için tekrarlar; Birincisi hızlı, sonra her ardışık biri yavaşlar.

My Code
3 ana parça vardır. Bu gerçek http sorgusu yapar yöntemi içeren

  • Downloader.cs (Downloader.cs ben zamanlı budur) Sorunun oluştuğunu nereye odaklanmak için gelen en iyi şekilde bunu basitleştirdik:

    public async Task<string> DownloadData(string requestString) 
    { 
        HttpResponseMessage response; 
        response = await this.httpClient.PostAsync(url, new StringContent(requestString)); 
        string returnString = await response.Content.ReadAsStringAsync(); 
        return returnString; 
    } 
    
  • QueryAgent.cs Bu yeni Downloader başlatırken ve arayan fonksiyonu ve bir döngü içinde aramaları uyumsuz yöntemi döngü ana uyumsuz içerir.
    Son yanıt kümesinin sonucuna bağlı olarak mümkün olan en kısa zamanda yeni istek göndermem gerekiyor (temelde, isteklerin dışarı çıkma oranı değişebilir, böylece sonunda bir Task.delay() yapıyorum.

    public async Task DownloadLoopAsync() 
    { 
        while (this.isLooping) 
        { 
         // I put this here to stop it using lots of cpu. 
         // Not sure if it is best practice. 
         // advice on how to do this kind of loop would be appreciated. 
         await Task.Delay(5); 
    
         foreach (string identifier in this.Identifiers) 
         { 
          string newRequestString = this.generateRequestString(identifier); 
          Task newRequest = RequestNewDataAsync(newRequestString); 
         } 
        } 
    }  
    
    public async Task RequestNewDataAsync(string requestString) 
    { 
        string responseString = await this.downloader.DownloadData(requestString); 
        this.ProcessedData = this.ProcessData(responseString); 
    } 
    
  • program.cs) while döngüsünün Bu programı durdurmak için sonunda o zaman uyumsuz QueryAgent içinde işlev ve bir Console.ReadLine() üzerine bir kez Görev çağrılarının sadece bir konsol programıdır çıkarken.HttpClient'i burada da başlatıyorum. Ben gibi, bir kez() DownloadLoop çağırır:

    QueryAgent myAgent = new QueryAgent(httpClient); 
    Task dataLoop = myAgent.DownloadLoopAsync(); 
    Console.Readline(); 
    

Ben ben DownloadLoopAsync içinde döngü yeni Görevi çağırıyorum yolu ile ilgili bir şey() sahiptir şüphesi Ancak bu bir anlam ifade etmiyor çünkü her bir yeni Görev'in kendi başına işleneceğini varsaydım.

Tuğla duvarına çarptığından, bu çalışmayı tasarlandığı gibi çalıştırmaya çalışırken tuğla duvarına çarptığınız için herhangi bir tavsiye büyük takdir görmektedir.

Teşekkürler. 1

DÜZENLEME Sadece çağıran Görev döngüde etrafında bir karmaşa vardı. Değiştim:

foreach(string identifier in this.Identifiers) 
{ 
    string newRequestString = this.generateRequestString(identifier); 
    Task newRequest = RequestNewDataAsync(newRequestString); 
} 

şimdi beklendiği gibi çalışmasını sağlayan

foreach(string identifier in this.Identifiers) 
{ 
    string newRequestString = this.generateRequestString(identifier); 
    Task newRequest = RequestNewDataAsync(newRequestString); 
    await Task.Delay(1); 
} 

için: Şimdi daha da karıştı bu işe görünmektedir neden olduğum

Time taken: 71.0088 milliseconds 
Time taken: 65.0499 milliseconds 
Time taken: 64.0955 milliseconds 
Time taken: 64.4291 milliseconds 
Time taken: 63.0841 milliseconds 
Time taken: 64.9841 milliseconds 
Time taken: 63.7261 milliseconds 
Time taken: 66.451 milliseconds 
Time taken: 62.4589 milliseconds 
Time taken: 65.331 milliseconds 
Time taken: 63.2761 milliseconds 
Time taken: 69.7145 milliseconds 
Time taken: 63.1238 milliseconds 

!

DÜZENLEME 2
aşağıdaki gibi zamanlama kodu mevcut Downloader.cs içinde:

public async Task<string> DownloadData(string requestString) 
{ 
    HttpResponseMessage response; 
    Stopwatch s = Stopwatch.StartNew(); 
    response = await this.httpClient.PostAsync(url, new StringContent(requestString)); 
    double timeTaken = s.Elapsed.TotalMilliseconds; 
    Console.WriteLine("Time taken: {0} milliseconds", timeTaken); 
    string returnString = await response.Content.ReadAsStringAsync(); 
    return returnString; 
} 

rağmen bekliyoruz Task.Delay (1); "hile yapmak" gibi görünüyor, ideal bir çözüm değil ne de neden çalıştığını anlayabiliyorum - belki bir şekilde RequestNewDataAsync Görevini başlatması için CPU zamanını verir ve bir çeşit kilitlenmeyi engeller mi? Sadece tahmin edebilirim ... Aynı zamanda, döngü 1000Hz'e sınırladığı anlamına gelir, bu da bir sınırdır, çünkü Task.Delay() yalnızca 1 değeri minimum olan tamsayı değerleri kabul eder.

DÜZENLEME 3
biraz daha okuma (Tüm bu nispeten yeni! Hala) yaparak, belki bu bir şey iyi (kısa ömürlü görevlerin 100s 10) ThreadPool'da kullanılarak bırakılır? Ancak bu, aşırı (yeni görev başına 1 MB) ek yük getirecekti; ya da yine farklı bir şey.

DÜZENLEME 4

biraz daha farklı yerlerde Task.Delay (1) koyarak denemeler yaptım.

  • [slowdown gerçekleşir] Downloader'da httpClient.PostAsync() 'den önce veya sonra yerleştirme.CS
  • [yavaşlama meydana] önce veya RequestNewDataAsync()
  • [yavaşlama/beklenen performans] önce veya DownloadLoopAsync() Görev çağrısının ardından yerleştirme olanağı sunmaktadır çağrısının ardından yerleştirilmesi
+0

nereye istekleri gönderene:

public async Task RequestNewDataAsync(string requestString) { string responseString = await this.downloader.DownloadData(requestString); // Note: you have a race condition here. // multiple downloads could complete simultaneously and try to concurrently // assign to ProcessedData. This may or may not be an issue for you. this.ProcessedData = this.ProcessData(responseString); } 

Ayrıca ben kullanıcıya sonra tamamlamak için son toplu beklemek üzere ana programı modifiye kilit bastığında için? sunucunun yükünü koymanız, dolayısıyla daha yavaş yanıt almanız mümkün mü? – Ewan

+0

'HttpClient' eşzamanlı olarak kullanılamaz, iş parçacığı güvenli değildir. Ayrıca, "RequestNewDataAsync" ile sonuçlanan görevi hiçbir zaman beklemezsiniz. Böylece, daha öncekiler tamamlanmadan önce daha fazla istek gönderiyorsunuz ve sunucuya daha fazla yük koyuyorsunuz. – Lukazoid

+0

yaklaşımınız standart dışı biraz görünüyor. Ben eklemek ve daha sonra beklediğiniz görevlerin bir liste var öneririm. Döngü yerine – Ewan

cevap

6

await'u yaptığınızda, sonraki kod yalnızca 'bekledikleriniz' tamamlandıktan sonra yürütülür.

Aynı anda birden çok sorgu göndermek için, her sorgu için yeni Task oluşturmanız ve ardından hepsini birlikte beklemeniz gerekir.

var tasks = new List<Task>(); 

foreach (string identifier in Identifiers) 
{ 
    var newRequestString = generateRequestString(identifier); 
    var newRequest = RequestNewDataAsync(newRequestString); 
    tasks.Add(newRequest); 
} 

Task.WhenAll(tasks); // synchronous wait for all tasks to complete 
+0

shr eklemelisiniz, bu benim anlayışımdı. DownloadLoopAsync() 'de yazdıklarıma bir göz atın. Her yeni tanımlayıcı için beklemeden yeni bir görev oluşturuyorum. Bu, her birinin uyumsuz olarak işleneceği anlamına gelmez mi? Ne demek istiyorsun - bir görev koleksiyonu yarat ve sonra bunları tek seferde işle. Https://msdn.microsoft.com/en-us/library/jj155756.aspx içinde olduğu gibi, bir sorgu listesi oluşturmak zorundayım, ancak bir WhenAny() kullanmak yerine, sadece .ToList() çağırın ve bırakın süreç kendi başına mı? – undercurrent

+0

Anthony, DownloadLoopAsync() uygulamasında yeni bir görev oluşturulmaz. Aslında, RequestNewDataAsync (newRequestString) çağrısı beri; Beklenmiyor, senkronize bir çağrı olacak. Evet, haklısınız, eşzamanlı istekleri göndermek için, bir dizi görev oluşturmalısınız. WhenAll() kullanarak birlikte görev koleksiyonunu bekleyebilirsiniz. – shr

+0

@Anthony, zamanları nasıl ölçtüğünüz dahil olmak üzere eksiksiz kodu gönderiyor musunuz? – shr

1

Büyük olasılıkla ağınızı veya uzak sunucunuzu isteklerle doyurursunuz. Sonsuz döngüsünüz, bir sonraki partiye başlamadan önce önceki partinin tamamlanmasını beklemez, bu nedenle, zamanla, toplu işlerin tamamlanabilmesinden daha hızlı bir şekilde ihracı gerçekleştirirsiniz.

Bir sonraki kümeye başlamadan önce bir toplu işin tamamlanmasını bekleyen değiştirilmiş bir döngü. Artık bir Task.Delay'u beklemediğine ve bunun yerine indirme görevlerinin toplanmasını beklediğine dikkat edin.

public async Task DownloadLoopAsync() 
{ 
    while (this.isLooping) 
    { 
     var tasks = this 
     .Identifiers 
     .Select(id => RequestNewDataAsync(this.generateRequestString(id)); 

     await Task.WhenAll(tasks); 
    } 
} 

İşlenen verilerin sonuçlarını nasıl depoladığınız konusunda bir yarış koşulunuz olduğunu unutmayın. Bu işlem olabilir veya sizin için bir sorun olmayabilir:

QueryAgent myAgent = new QueryAgent(httpClient); 
Task dataLoop = myAgent.DownloadLoopAsync(); 
Console.Readline(); 
myAgent.isLooping = false; 
dataLoop.Wait(); // let the last set of downloads complete 
+0

Orijinal kod, eşzamanlı sözlükler kullanır; Buraya yazı yazmayı basitleştirdim. Task.WhenAll kullanarak mantıklı, ancak herhangi bir yeni istekleri gönderilmeden önce bu cevap vermek için bir toplu iş gerektirmez? İletimi resepsiyonla eşleştirirsem, her şey artık isteğim, sunucu yanıtı, bana geri gönderme ve indirme işlemi arasındaki gecikmeye bağlı. Eğer 10Hz veriyi istiyorsam ve sunucunun bu hızdaki istekleri karşılayabileceğini biliyorum, ancak ping'im sadece 3hz'e izin veriyor, bunu nasıl başarabilirim? Belki de sunucu sonu değil, ama benim sınırım sınırlı. – undercurrent

İlgili konular