2016-01-06 30 views
10

我有30個子公司,每個人都實施了他們的網絡服務(使用不同的技術)。調用許多Web服務的最佳方法?

我需要實現一個Web服務來聚合它們,例如,所有子公司Web服務都有一個名爲GetUserPoint(int nationalCode)的Web方法,我需要實現我的Web服務,它將調用所有這些服務並收集所有的響應(例如點數的總和)。

這是我的基類:

public abstract class BaseClass 
{ // all same attributes and methods 
    public long GetPoint(int nationalCode); 
} 

對於每個子企業的Web服務,我實現了繼承這個基類的類和定義自己GetPoint方法。

public class Company1 
{ 
    //implement own GetPoint method (call a web service). 
} 

public class CompanyN 
{ 
    //implement own GetPoint method (call a web service). 
} 

所以,這是我的Web方法:

 [WebMethod] 
     public long MyCollector(string nationalCode) 
     { 

      BaseClass[] Clients = new BaseClass[] { new Company1(),//... ,new Company1()} 

      long Result = 0; 
      foreach (var item in Clients) 
      { 
       long ResultTemp = item.GetPoint(nationalCode); 
       Result += ResultTemp; 
      } 
     return Result; 
     } 

OK,它的工作原理,但它是如此緩慢,是因爲每個子公司的Web服務在不同的服務器託管(在網上)。

我可以使用並行編程是這樣的:(這是所謂的並行編程!?)

foreach (var item in Clients) 
    { 
        Tasks.Add(Task.Run(() => 
         { 
         Result.AddRange(item.GetPoint(MasterLogId, mobileNumber));     
        } 
     } 

我認爲平行編程(和線程)不利於這個解決方案,因爲我的解決辦法是IO綁定(而不是CPU密集型)!

調用每個外部Web服務是如此之慢,我說得對嗎?很多正在等待得到響應的線程!

我認爲異步編程是最好的方法,但我是異步編程和並行編程的新手。

什麼是最好的方法? (parallel.foreach - async TAP - async APM - async EAP -threading)

請爲我寫一個例子。

+0

你已經掌握了所有的基礎知識。這是一個非常好的開始。 'Parallel.ForEach'和'Thread's遭遇與Task.Run相同的問題 - 它們對於CPU來說是好事,而不是對IO進行綁定的工作。 TAP絕對是最容易編寫的(你基本上保持當前的循環,但IO的東西變得「無螺紋」和非常可擴展的)。 APM和EAP很容易轉換爲TAP(通過APM的'Task.Factory.FromAsync'和EAP的TaskCompletionSource '),所以如果這是您現有的API提供的,您可以使用TAP並插入APM和EAP調用。 –

回答

7

看到有人做了功課很讓人耳目一新。在.NET 4中,首先要做的第一件事情(現在情況仍然如此)TAP是.NET中異步工作流的首選技術。任務很容易組合,如果您提供真正的Task<T>返回的API,則您可以輕鬆地並行處理Web服務調用。現在你已經用「Task.Run」「僞造」了它,並且暫時這可能足以滿足你的目的。當然,你的線程池線程會花費很多時間阻塞,但是如果服務器負載不是很高,即使它不是理想的事情,你也可以很好地避開它。

你只需要在你的代碼中修正一個潛在的競爭條件(更多的是在最後)。

如果您想要遵循最佳做法,那麼您可以使用真正的TAP。如果您的API提供了Task開箱即用方法,那很簡單。如果不是,那麼APM和EAP可以很容易地轉換爲TAP,這不是遊戲結束。 MSDN參考:https://msdn.microsoft.com/en-us/library/hh873178(v=vs.110).aspx

我還會在這裏列出一些轉換示例。

APM(從另一個SO問題採取):

MessageQueue不提供ReceiveAsync方法,但我們可以得到它通過Task.Factory.FromAsync打太極:

public static Task<Message> ReceiveAsync(this MessageQueue messageQueue) 
{ 
    return Task.Factory.FromAsync(messageQueue.BeginReceive(), messageQueue.EndPeek); 
} 

... 

Message message = await messageQueue.ReceiveAsync().ConfigureAwait(false); 

如果你的Web服務代理有BeginXXX/EndXXX方法,這是要走的路。

EAP

假設你有一個從SoapHttpClientProtocol衍生老網絡服務代理,只有基於事件的異步方法。你可以將它們轉換爲TAP如下:

public Task<long> GetPointAsyncTask(this PointWebService webService, int nationalCode) 
{ 
    TaskCompletionSource<long> tcs = new TaskCompletionSource<long>(); 

    webService.GetPointAsyncCompleted += (s, e) => 
    { 
     if (e.Cancelled) 
     { 
      tcs.SetCanceled(); 
     } 
     else if (e.Error != null) 
     { 
      tcs.SetException(e.Error); 
     } 
     else 
     { 
      tcs.SetResult(e.Result); 
     } 
    }; 

    webService.GetPointAsync(nationalCode); 

    return tcs.Task; 
} 

... 

using (PointWebService service = new PointWebService()) 
{ 
    long point = await service.GetPointAsyncTask(123).ConfigureAwait(false); 
} 

彙總結果

至於何時聚集並行結果避免種族,你的TAP循環代碼是幾乎的權利,但你需要避免突變在你的Task機構內共享狀態,因爲它們可能會並行執行。在你的情況下共享狀態是Result - 這是某種集合。如果這個集合不是線程安全的(即,如果它是一個簡單的List<long>),那麼你有競爭條件,你可能會在Add我認爲AddRange在你的代碼是一個錯字,但可能會得到例外和/如果不是的話 - 以上仍然適用)。

一個簡單的異步友好重寫,修復你的比賽會是這樣的:

List<Task<long>> tasks = new List<Task<long>>(); 

foreach (BaseClass item in Clients) { 
    tasks.Add(item.GetPointAsync(MasterLogId, mobileNumber));     
} 

long[] results = await Task.WhenAll(tasks).ConfigureAwait(false); 

如果您決定偷懶,與現在Task.Run液粘,修正後的版本將是這樣的:

List<Task<long>> tasks = new List<Task<long>>(); 

foreach (BaseClass item in Clients) 
{ 
    Task<long> dodgyThreadPoolTask = Task.Run(
     () => item.GetPoint(MasterLogId, mobileNumber) 
    ); 

    tasks.Add(dodgyThreadPoolTask);     
} 

long[] results = await Task.WhenAll(tasks).ConfigureAwait(false); 
2

您可以創建用GetPoint的異步版本:

public abstract class BaseClass 
{ // all same attributes and methods 
    public abstract long GetPoint(int nationalCode); 

    public async Task<long> GetPointAsync(int nationalCode) 
    { 
     return await GetPoint(nationalCode); 
    } 
} 

然後,收集每個客戶端調用的任務。之後,使用Task.WhenAll執行所有任務。這將同時執行它們。此外,由於通過Kirill指出的那樣,你可以等待每個任務的結果:

var tasks = Clients.Select(x => x.GetPointAsync(nationalCode)); 
long[] results = await Task.WhenAll(tasks); 

如果你不想讓聚集方法異步,您可以通過調用。結果,而不是等待,喜歡收集結果所以:

long[] results = Task.WhenAll(tasks).Result; 
+1

我的意思是'var results = await Task.WhenAll(任務);' - 對不起,應該更清楚了。 –

相關問題