2016-01-06 31 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(任务);' - 对不起,应该更清楚了。 –

相关问题