2017-05-08 66 views
2

我注意到在我的流程中發生了初始減速,並且在進行多次hangdumps時,我能夠使用以下代碼隔離問題並重現該情形。我正在使用一個具有鎖定功能的庫,它最終會調用某些方法的用戶端實現。這些方法使用httpclient進行異步調用。這些異步調用是從庫內的這些鎖中進行的。使用具有鎖定的異步調用速度減慢

現在,我正在發生什麼的理論(糾正我,如果我錯了): 獲取旋轉的任務嘗試獲取鎖並保持足夠快的線程,以便第一個PingAsync方法需要等待默認的任務調度程序啓動一個新的線程以便運行,這是基於默認的.net調度算法的0.5秒。這就是爲什麼我認爲我注意到任務總數大於32的延遲,這也隨着總任務數的增加而線性增加。

解決方法:

  1. 增加minthreads算,我認爲這是治標,而不是實際的問題。
  2. 另一種方法是有一個有限的併發來控制任務的數量。但這些都是由傳入HTTPRequests的網絡服務器紡任務,通常我們不會擁有控制權(還是我們?)

我明白,結合ASYC和非異步是糟糕的設計,並使用sempahores'異步調用將是更好的方式去。假設我無法控制這個圖書館,那麼如何減輕這個問題呢?

const int ParallelCount = 16; 
    const int TotalTasks = 33; 

    static object _lockObj = new object(); 
    static HttpClient _httpClient = new HttpClient(); 
    static int count = 0; 

    static void Main(string[] args) 
    { 
     ThreadPool.GetMinThreads(out int workerThreads, out int ioThreads); 
     Console.WriteLine($"Min threads count. Worker: {workerThreads}. IoThreads: {ioThreads}"); 

     ThreadPool.GetMaxThreads(out workerThreads, out ioThreads); 
     Console.WriteLine($"Max threads count. Worker: {workerThreads}. IoThreads: {ioThreads}"); 

     //var done = ThreadPool.SetMaxThreads(1024, 1000); 
     //ThreadPool.GetMaxThreads(out workerThreads, out ioThreads); 
     //Console.WriteLine($"Set Max Threads success? {done}."); 
     //Console.WriteLine($"Max threads count. Worker: {workerThreads}. IoThreads: {ioThreads}"); 

     //var done = ThreadPool.SetMinThreads(1024, 1000); 
     //ThreadPool.GetMinThreads(out workerThreads, out ioThreads); 
     //Console.WriteLine($"Set Min Threads success? {done}."); 
     //Console.WriteLine($"Min threads count. Worker: {workerThreads}. IoThreads: {ioThreads}"); 

     var startTime = DateTime.UtcNow; 
     var tasks = new List<Task>(); 

     for (int i = 0; i < TotalTasks; i++) 
     { 
      tasks.Add(Task.Run(() => LibraryMethod())); 

      //while (tasks.Count > ParallelCount) 
      //{ 
      // var task = Task.WhenAny(tasks.ToArray()).GetAwaiter().GetResult(); 

      // if (task.IsFaulted) 
      // { 
      //  throw task.Exception; 
      // } 

      // tasks.Remove(task); 
      //} 
     } 

     Task.WaitAll(tasks.ToArray()); 

     //while (tasks.Count > 0) 
     //{ 
     // var task = Task.WhenAny(tasks.ToArray()).GetAwaiter().GetResult(); 

     // if (task.IsFaulted) 
     // { 
     //  throw task.Exception; 
     // } 

     // tasks.Remove(task); 

     // Console.Write("."); 
     //} 

     Console.Write($"\nDone in {(DateTime.UtcNow-startTime).TotalMilliseconds}"); 
     Console.ReadLine(); 
    } 

假設這是其中文庫的方法被稱爲部分,

public static void LibraryMethod() 
    { 
     lock (_lockObj) 
     { 
      SimpleNonAsync(); 
     } 
    } 

最終,該方法的用戶執行被調用其是異步。

public static void SimpleNonAsync() 
    { 
      //PingAsync().Result; 
      //PingAsync().ConfigureAwaiter(false).Wait(); 
      PingAsync().Wait(); 
    } 

    private static async Task PingAsync() 
    { 
     Console.Write($"{Interlocked.Increment(ref count)}."); 

     await _httpClient.SendAsync(new HttpRequestMessage 
     { 
      RequestUri = new Uri([email protected]"http://127.0.0.1"), 
      Method = HttpMethod.Get 
     }); 
    } 
+1

異步調用上的阻塞破壞了異步的整個目的。 – SLaks

+0

是的。我明白那個。考慮到我沒有修改這個庫的權限,你是否建議使用非異步httpclient調用(假設有一個),這會影響性能,因爲它是一個io操作? – iambatman

+1

請注意[任務(仍然)不是線程](https://blogs.msdn.microsoft.com/benwilli/2015/09/10/tasks-are-still-not-threads-and-async-is-not -parallel /)... –

回答

2

這些異步調用從庫中,這些鎖內進行。

這是一個設計缺陷。沒有人應該在鎖定的情況下調用任意代碼。

也就是說,鎖與您所看到的問題無關。

我知道結合asyc和非異步是不好的設計,並且使用sempahores的異步調用將是更好的方法。假設我無法控制這個圖書館,那麼如何減輕這個問題呢?

問題庫迫使你的代碼是同步的。這意味着每個下載都會阻塞一個線程;只要圖書館的回調是同步的,就沒有辦法。

增加minthreads計數,我認爲這是治療症狀,而不是實際問題。

如果你不能修改庫,那麼你必須使用每個請求一個線程,這成爲一個可行的解決方法。你來治療症狀,因爲你不能解決問題(即圖書館)。

另一種方法是有一個有限的併發來控制任務的數量。但是這些都是web服務器針對傳入httprequests所執行的任務,通常我們無法控制它(或者我們會嗎?)

不;導致問題的任務是您使用Task.Run自行旋轉的任務。服務器上的任務是完全獨立的;你的代碼不能影響甚至檢測它們。

如果您希望在不等待線程注入的情況下獲得更高的併發性,那麼您需要增加最小線程數,並且您還可能需要增加ServicePointManager.DefaultConnectionLimit。然後,您可以繼續使用Task.Run,或(如我所願)或Parallel或並行LINQ來執行並行處理。 Parallel/Parallel LINQ的一個很好的方面是它具有內置的節流支持,如果這也是需要的話。