3

如果我有这样定义的服务:

[ServiceContract(SessionMode = SessionMode.NotAllowed)]
public interface IMyService
{
    [OperationContract(IsOneWay = true)]
    [ReceiveContextEnabled(ManualControl = true)]
    void DoSomething(Message<XElement> 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<XElement> message, AsyncCallback callback, object state);
    void EndDoSomething(IAsyncResult result);
}

而是这样做

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

.... some other code

myService.EndDoSomething(result);

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

4

2 回答 2

12

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

CLR 线程池将线程分为两种类型:worker 和 I/O(有关它们的更多信息,您可以在Simple description of worker and I/O threads in .NETMSDN中找到)。一般来说,线程池为您提供每个核心 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);

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

APM 方法用于单向 WCF 操作。

对于 WCF 合同:

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

     void EndDoSomething(IAsyncResult result);
 }

让我们使用下一个代码从客户端向服务器发送 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);
}

输出是:

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 underhood):

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/await 特性;
  • 异步执行 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
于 2013-06-29T13:29:04.400 回答
1

OneWay = true

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

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

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

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

AsyncPattern = true

如果您希望客户端等待操作结束(例如在它开始另一个操作之前),这很有用。如果是OneWay,客户端将发送一个请求并忘记它 - 它不关心发生了什么。当AsyncPattern服务完成执行该方法时,客户端将等待通知。

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

PS。关于您帖子的这一部分,我有点不确定:“使用不是从 svcutil 生成的共享合同或添加服务参考”。我认为这对我的回答并不重要,但以防万一,我将在此处保留此免责声明。;)

于 2013-06-27T08:55:45.383 回答