在 WCF服务异步和使用客户端上的任务调用同步方法之间是否存在显着差异?

如果我有一个定义的服务:

[ServiceContract(SessionMode = SessionMode.NotAllowed)] public interface IMyService { [OperationContract(IsOneWay = true)] [ReceiveContextEnabled(ManualControl = true)] void DoSomething(Message message); } 

我想从我的客户端异步调用它(使用不从svcutil生成的共享契约或添加服务引用)我可以这样做:

 Task task = Task.Factory.StartNew(() => myService.DoSomething(message)); ... some other code task.Wait(); 

我也可以将我的服务定义为异步:

 [ServiceContract(SessionMode = SessionMode.NotAllowed)] public interface ICacheKeyExchangeAsync { [OperationContract(IsOneWay = true, AsyncPattern = true)] [ReceiveContextEnabled(ManualControl = true)] IAsyncResult BeginDoSomething(Message message, AsyncCallback callback, object state); void EndDoSomething(IAsyncResult result); } 

而是这样做

 IAsyncResult result = myService.BeginDoSomething(message, null, null); .... some other code myService.EndDoSomething(result); 

这些方法之间是否存在显着差异?

是的, 线程池线程利用率存在差异。

CLR线程池在两种类型上分割线程:worker和I / O(有关它们的更多信息,可以在.NET和MSDN上的工作者和I / O线程的简单描述中找到)。 一般来说,线程池为每个核心提供250个工作线程和1000个I / O线程,因此您可以使用工作线程来处理您的WCF服务输入,并使用I / O线程来等待异步发送/接收操作完成(这是支持的)在Windows操作系统级别上通过重叠I / O机制)。

记住这一点,让我们看一下使用ThreadPool.GetAvailableThreads()方法将两种方法用于哪些线程:

  int worker; int ioCompletion; ThreadPool.GetAvailableThreads(out worker, out ioCompletion); Console.WriteLine("{0} worker and {1} I/O threads are available", worker, ioCompletion); 

我将仅显示客户端的线程池利用率结果,但对于服务器端也是如此。

用于单向WCF操作的APM方法。

对于WCF合同:

  [ServiceContract] public interface IService1 { [OperationContract(IsOneWay = true, AsyncPattern = true)] IAsyncResult BeginDoSomething(int value, AsyncCallback callback, object state); void EndDoSomething(IAsyncResult result); } 

让我们使用下一个代码从客户端向服务器发送100个请求:

 ChannelFactory channelFactory = new ChannelFactory(); var client = channelFactory.CreateChannel(); for (int i = 0; i < 100; i++) { int worker; int ioCompletion; ThreadPool.GetAvailableThreads(out worker, out ioCompletion); Console.WriteLine("{0} worker and {1} I/O threads are available", worker, ioCompletion); client.BeginDoSomething(i, asyncCallback, null); } 

输出是:

 1023 worker and 1000 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 996 I/O threads are available 1023 worker and 996 I/O threads are available 1023 worker and 996 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 999 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 999 I/O threads are available 1023 worker and 999 I/O threads are available 1023 worker and 999 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 999 I/O threads are available 

如您所见,我的x4核心机器上可以使用所有工作线程,并且正在使用多个I / O线程。

作为TPL任务运行同步单向操作。

对于WCF合同:

  [ServiceContract] public interface IService2 { [OperationContract(IsOneWay = true)] void DoSomething(int value); } 

让我们使用下一个代码运行从客户端到服务器的100个请求(只是想注意TPL使用CLR ThreadPool发动机罩):

 for (int i = 0; i < 100; i++) { int worker; int ioCompletion; ThreadPool.GetAvailableThreads(out worker, out ioCompletion); Console.WriteLine("{0} worker and {1} I/O threads are available", worker, ioCompletion); Task.Run(() => client.DoSomething(i)); } 

输出是:

 1023 worker and 1000 I/O threads are available 1022 worker and 1000 I/O threads are available 1021 worker and 1000 I/O threads are available 1020 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 1019 worker and 1000 I/O threads are available 

如您所见,现在正在使用工作线程,但不使用I / O线程。

那么,推荐的方法是什么?

总之,您的解决方案应该:

  • 利用线程池中的工作线程和I / O线程(特别是对于高负载的应用程序)来防止瓶颈;
  • 在Task中包装异步操作,这样你就可以获得TPL和新的C#async / awaitfunction的所有好处;
  • 以异步方式执行OneWay操作是绝对合理的(考虑到,有时OneWay实际上不是OneWay )。

因此,推荐的方法是WCF的基于任务的异步模式 ,满足上述所有要求。

WCF的基于任务的异步模式。

合同:

 [ServiceContract] public interface IService3 { [OperationContract(IsOneWay = true)] Task DoSomethingAsync(int value); } 

让我们再次发送100个请求:

 for (int i = 0; i < 100; i++) { int worker; int ioCompletion; ThreadPool.GetAvailableThreads(out worker, out ioCompletion); Console.WriteLine("{0} worker and {1} I/O threads are available", worker, ioCompletion); client.DoSomethingAsync(i); } 

输出:

 1023 worker and 1000 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 999 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 999 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 999 I/O threads are available 1023 worker and 999 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 999 I/O threads are available 1023 worker and 998 I/O threads are available 1023 worker and 999 I/O threads are available 

OneWay = true

如果使用OneWay属性,客户端将不会等待服务完成方法的执行。 您可以通过创建一个除了等待之外什么都不做的服务方法来轻松地测试它。 客户端将调用服务方法(甚至同步)并继续。

您可以通过在服务中编写一个简单的测试方法来非常轻松地进行测试:

  public void Test() { System.Threading.Thread.Sleep(TimeSpan.FromSeconds(30)); } 

使用和不使用OneWay属性调用它时检查行为。 因此,以异步方式调用OneWay方法有点没有意义,虽然我怀疑这样做意味着你正在推动非常小的事情(比如创建请求并将你发送的任何数据发送到另一个线程),所以它可能仍然有用。

AsyncPattern = true

如果您希望客户端等待操作结束(例如,在启动另一个操作之前),这将非常有用。 在OneWay情况下,客户端将发送请求并忘记它 – 它不关心发生了什么。 使用AsyncPattern ,客户端将在服务完成执行该方法时等待通知。

该模式还有一个额外的好处 – 如果需要,它允许您在方法完成服务执行时运行一些代码。 例如,在创建DuplexService时,它非常有用,它需要管理客户端处理程序并在发生某些事件时向客户端发送通知。

PS。 关于post的这一部分,我有点不确定:“ 使用不从svcutil生成的共享契约或添加服务引用 ”。 我不认为这对我的答案很重要,但为了以防万一,我在这里放弃这个免责声明。 ;)