Is there a significant difference between making a

2019-04-01 02:45发布

If I have a service defined so:

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

and I want to call it asyncronously from my client (using shared contracts not generating from svcutil or add service reference) I can do:

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

... some other code

task.Wait();

I could also define my service to be async:

[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);
}

and do this instead

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

.... some other code

myService.EndDoSomething(result);

Are there significant differences between the approaches?

2条回答
Lonely孤独者°
2楼-- · 2019-04-01 03:00

Yes, there are a differences in Thread Pool threads utilization.

CLR thread pool splitts threads on two types: worker and I/O (more information about them you can find in Simple description of worker and I/O threads in .NET and on MSDN). Generally speaking, thread pool gives you 250 worker threads per core and 1000 I/O threads, so you can use worker threads to process your WCF service input, and I/O threads to wait for asynchronous send/receive operation completion (which is supported on Windows OS level by overlapped I/O mechanism).

Keeping above in mind, let's have a look which threads are being utilized for both approaches by using ThreadPool.GetAvailableThreads() method:

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

I'll just show results of Thread Pool utilization for client side, but it's the same for server side as well.

APM approach for one way WCF operation.

For WCF contract:

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

     void EndDoSomething(IAsyncResult result);
 }

Lets send 100 requests from client to server using next code:

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);
}

Output is:

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

As you can see all worker threads are available on my x4 core machine and several I/O threads are being utilized.

Running synchronous one way operation as TPL Task.

For WCF contract:

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

Let's run 100 requests from client to server using next code (just want to notice that TPL uses 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));
}

Output is:

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

As you can see, now worker threads are being utilized, but not I/O threads.

So, what's the recommended approach?

As summary, your solution should:

  • Utilize worker and I/O threads from thread pool (especially, for highlyloaded applications) to prevent bottlenecks;
  • Wrap asynchronous operation in Task, so you can get all benefits of TPL and new C# async/await features;
  • It's absolutely reasonable to execute OneWay operation asynchronously (considering, sometimes OneWay is not OneWay actually).

So, recommended approach is Task-based asynchronous pattern for WCF, that satisfies all requirements above.

Task-based asynchronous pattern for WCF.

For contract:

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

Lets send 100 requests again:

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);
}

Output:

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
查看更多
Emotional °昔
3楼-- · 2019-04-01 03:14

OneWay = true

If you use the OneWay attribute, the client won't wait for the service to finish execution of the method. You can test that out easily by creating a service method which doesn't do anything but waits. A client will call the service method (even synchronously) and move on.

You can test that very easily by writing a simple test method in your service:

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

and check the behaviour when you call it with and without the OneWay attribute. As such, it's somewhat pointless to call a OneWay method it asynchronously, although I suspect doing so means you're pushing very minor things (like creating the request and sending whatever data you send) to another thread, so it might still be useful.

AsyncPattern = true

This is useful if you want the client to wait for the operation to end (before it starts another for example). In case of OneWay, the client will send a request and forget about it - it doesn't care what's going on. With an AsyncPattern the client will wait for a notification when the service finishes executing the method.

The pattern also has one more added benefit - if you need it, it allows you to run some code when the method finishes execution on the service. It's useful when, for example, creating a DuplexService, which needs to manage client handlers and send notifications to clients when certain events occur.

PS. I'm a bit uncertain with regards to this part of your post: "using shared contracts not generating from svcutil or add service reference". I don't think it matters for my answer, but just in case, I'm leaving this disclaimer here. ;)

查看更多
登录 后发表回答