2017-10-04 11 views
0

我正在使用一些不返回詳細程度的API,因此我需要在第一次調用後進行其他API調用以獲取所需的詳細信息。我試圖通過多線程的附加調用來提高性能。下面是代碼的簡化版本我目前:無法獲得多個併發API調用來持續更新響應對象

public class Customer 
    { 
     public int CustomerId { get; set; } 
     public string CustomerName { get; set; } 
    } 

    public class Order 
    { 
     public int OrderId { get; set; } 
     public string ShippingAddress { get; set; } 
    } 

    public class OrderPayment 
    { 
     public int OrderId { get; set; } 
     public bool PaymentIsComplete { get; set; } 
    } 

    public class OrderItem 
    { 
     public int OrderItemId { get; set; } 
     public string ItemName { get; set; } 
     public int CustomerId { get; set; } 
     public int OrderId { get; set; } 
    } 

    public class OrderItemWithDetails : OrderItem 
    { 
     public Customer Customer { get; set; } 
     public Order Order { get; set; } 
     public OrderPayment Payment { get; set; } 
    } 

    public async Task<List<OrderItemWithDetails>> GetOrderItemsWithDetailsAsync() 
    { 
     var url = "orderItemsUrl"; 
     var tuple = await GetAsync<List<OrderItemWithDetails>>(url, null, null, null); 
     List<OrderItemWithDetails> response = tuple.Item1; 

     List<Task> taskList = new List<Task>(); 

     IEnumerable<int> customerIds = response.Select<OrderItemWithDetails, int>(o => o.CustomerId).Distinct(); 
     foreach (var customerId in customerIds) 
     { 
      int id = customerId; 
      taskList.Add(Task.Run(async() => 
      { 
       url = "customerUrl?CustomerId=" + id; 
       var customerTuple = await GetAsync<Customer>(url, null, null, null); 
       response.Where(i => i.CustomerId == id).ToList().ForEach(i => i.Customer = customerTuple.Item1); 
      })); 
     } 

     IEnumerable<int> orderIds = response.Select<OrderItemWithDetails, int>(o => o.OrderId).Distinct(); 
     foreach (var orderId in orderIds) 
     { 
      int id = orderId; 
      taskList.Add(Task.Run(async() => 
      { 
       url = "orderUrl?OrderId=" + id; 
       var orderTuple = await GetAsync<Order>(url, null, null, null); 
       response.Where(i => i.OrderId == id).ToList().ForEach(i => i.Order = orderTuple.Item1); 
      })); 

      taskList.Add(Task.Run(async() => 
      { 
       url = "paymentUrl?OrderId=" + id; 
       var paymentTuple = await GetAsync<OrderPayment>(url, null, null, null); 
       response.Where(i => i.OrderId == id).ToList().ForEach(i => i.Payment = paymentTuple.Item1); 
      })); 
     } 

     await Task.WhenAll(taskList); 

     return response; 
    } 

的GetAsync基本上是圍繞HttpClient.GetAsync一個包裝與一些額外的邏輯,包括處理解析JSON轉換成一個對象。我知道該電話中的代碼很好,它已被用於一些其他2年以上的電話。我一直遇到的兩個問題是,它不會等待任務完成,因此OrderItemWithDetails中的其他對象不會一致地填充,並且偶爾Task.WhenAll會在IEnumerable上引發異常,該異常不能爲null。我曾經嘗試過的以下更改每一個組合,一些工作更穩定比別人,但他們沒有工作時間的100%:

  • 變化Task.Run到Task.Factory.StartNew
  • 更改匿名方法不能異步和更改的await GetAsync到GetAsync.Result
  • 變化Task.WhenAll到Task.WaitAll
  • 使用ContinueWith並在GetAsync另一個匿名方法調用來處理更新響應對象
  • 構建創建任務之前的url,改變匿名方法直接指向GetAsync,並在所有任務運行後將結果添加到響應中
  • 創建一個單獨的「狀態」列表,在每個任務向狀態「NotDone」添加條目之前添加一行每個任務的最終更新狀態爲任務「完成」,並與其中循環,等待列表中的所有條目將被「完成」

即使更換Taks.WhenAll我知道的最後一個選項是修復這個問題的完全錯誤的方法並不奏效(仍然以響應中的一堆空屬性結束),這完全讓我感到困惑。我已經爲此工作了好幾天了,Google已經瘋狂了,並且仍然沒有接近正常工作的代碼。任何幫助將非常感激。

回答

0

在離開這幾天後,我回來找出問題所在。雖然問題似乎是它並沒有等待所有的任務完成,因爲一些子對象沒有被設置,這不是問題。正如我在有限的多線程調試中發現的那樣,調試非常困難,因爲代碼是非線性的。問題是我重新使用url變量,所以在調用API的時候url已經在其他任務之一中更新了。所以http調用是「成功」的,並且返回了一個對象,但不是所期望的對象,因此Json解析器生成了一個空對象,所以我的響應對象被設置爲一個空對象。在上面的代碼中實際上不會發生空引用錯誤,在我簡化它之前的原始版本必須對其中一個響應失敗時的枚舉進行枚舉。