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

Sam*_*der 3 c# wcf multithreading asynchronous

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

[ServiceContract(SessionMode = SessionMode.NotAllowed)]
public interface IMyService
{
    [OperationContract(IsOneWay = true)]
    [ReceiveContextEnabled(ManualControl = true)]
    void DoSomething(Message<XElement> message);
}
Run Code Online (Sandbox Code Playgroud)

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

Task task = Task.Factory.StartNew(() => myService.DoSomething(message));

... some other code

task.Wait();
Run Code Online (Sandbox Code Playgroud)

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

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

而是这样做

IAsyncResult result = myService.BeginDoSomething(message, null, null);

.... some other code

myService.EndDoSomething(result);
Run Code Online (Sandbox Code Playgroud)

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

Vla*_*hov 11

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

CLR线程池分为两种类型的线程:worker和I/O(有关它们的更多信息,可以在.NETMSDN的工作线程和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);
Run Code Online (Sandbox Code Playgroud)

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

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

对于WCF合同:

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

     void EndDoSomething(IAsyncResult result);
 }
Run Code Online (Sandbox Code Playgroud)

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

ChannelFactory<IService1> channelFactory = new ChannelFactory<IService1>();
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);
}
Run Code Online (Sandbox Code Playgroud)

输出是:

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
Run Code Online (Sandbox Code Playgroud)

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

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

对于WCF合同:

 [ServiceContract]
 public interface IService2
 {
     [OperationContract(IsOneWay = true)]
     void DoSomething(int value);
 }
Run Code Online (Sandbox Code Playgroud)

让我们使用下一个代码运行从客户端到服务器的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));
}
Run Code Online (Sandbox Code Playgroud)

输出是:

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

合同:

[ServiceContract]
public interface IService3
{
    [OperationContract(IsOneWay = true)]
    Task DoSomethingAsync(int value);
}
Run Code Online (Sandbox Code Playgroud)

让我们再次发送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);
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)