当InstanceContextMode为具有Net.Tcp绑定的WCF服务的PerCall时,Multiple的ConcurrencyMode是否具有相关性? [英] Does ConcurrencyMode of Multiple have relevance when InstanceContextMode is PerCall for a WCF service with Net.Tcp binding?

查看:62
本文介绍了当InstanceContextMode为具有Net.Tcp绑定的WCF服务的PerCall时,Multiple的ConcurrencyMode是否具有相关性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直认为,即使使用net.tcp之类的会话感知绑定,将InstanceContextMode设置为PerCall也会使并发模式无关紧要.这就是MSDN所说的 http://msdn.microsoft.com/en-us/library/ms731193. aspx 在PerCallinstancing中,并发无关紧要,因为每条消息都由新的InstanceContext处理,因此在InstanceContext中活动的线程永远不会超过一个."


但是今天我正在阅读Juval Lowy的《 Programming WCF Services编程》一书,他在第8章中写道

如果按呼叫服务具有传输级别的会话,则是否 允许并发处理呼叫是服务的产物 并发模式.如果服务配置有 ConcurrencyMode.Single,未决的并发处理 不允许使用呼叫,并且一次只能调度一次呼叫. 我认为这是一个有缺陷的设计.如果服务是 使用ConcurrencyMode配置.多个并发处理是 允许的.呼叫会在到达时分派,每个呼叫都会传到新实例, 并发执行.有趣的观察是 考虑到吞吐量,最好配置一个 带有ConcurrencyMode.Multiple的每次调用服务—实例本身 仍然是线程安全的(因此您不会发生同步 责任),但您将允许来自同一客户的并发呼叫.


这与我的理解和MSDN所说的相矛盾.哪个是正确的 ? 就我而言,我有一个WCF Net.Tcp服务,该服务使用了我的许多客户端应用程序,这些服务创建了一个新的代理对象,进行了调用,然后立即关闭了代理.该服务具有PerCall InstanceContextMode.如果将InstanceContextMode更改为Multiple且线程安全行为不比percall差,是否可以提高吞吐量?

解决方案

阅读Lowy的声明中的关键词是"以提高吞吐量". Lowy指出,当使用ConcurrencyMode.Single WCF时,将盲目实现一个锁,以强制对服务实例进行序列化.锁很昂贵,并且不需要,因为PerCall已经保证第二个线程将永远不会尝试调用同一服务实例.

在行为方面: 对于PerCall服务实例,ConcurrencyMode无关紧要.

在效果方面: 一个名为ConcurrencyMode.Multiple的PerCall服务应该稍​​快一些,因为它不会创建和获取ConcurrencyMode.Single正在使用的(不需要的)线程锁.

我编写了一个快速基准程序,以查看我是否可以衡量PerCall服务的单项性能与多项性能的影响: 基准测试没有显示出有意义的差异.

如果您想自己运行,我粘贴了以下代码.

我尝试过的测试用例:

  • 600个线程调用服务500次
  • 200个线程调用服务1000次
  • 8个线程调用服务10000次
  • 1个线程调用服务10000次

我在运行Service 2008 R2的4 CPU VM上运行了此命令.除了1个线程外,其他所有线程都受CPU限制.

结果: 所有的运行都在彼此的5%之内. 有时ConcurrencyMode.Multiple更快.有时ConcurrencyMode.Single更快.也许适当的统计分析可以选出一个赢家.我认为它们足够接近而无所谓.

这是典型的输出:

在net.pipe://localhost/base上启动 Single 服务... 类型= SingleService ThreadCount = 600 ThreadCallCount = 500 运行时间:45156759滴答声 12615毫秒

在net.pipe://localhost/base上启动 Multiple 服务... 类型= MultiService ThreadCount = 600 ThreadCallCount = 500 运行时间:48731273滴答声 13613毫秒

在net.pipe://localhost/base上启动 Single 服务... 类型= SingleService ThreadCount = 600 ThreadCallCount = 500 运行时间:48701509次滴答声:13605毫秒

在net.pipe://localhost/base上启动 Multiple 服务... 类型= MultiService ThreadCount = 600 ThreadCallCount = 500 运行时:48590336滴答声:13574毫秒

基准代码:

通常的警告:这是基准代码,它采用了不适用于生产用途的捷径.

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WCFTest
{
    [ServiceContract]
    public interface ISimple
    {
        [OperationContract()]
        void Put();
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
    public class SingleService : ISimple
    {
        public void Put()
        {
            //Console.WriteLine("put got " + i);
            return;
        }
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class MultipleService : ISimple
    {
        public void Put()
        {
            //Console.WriteLine("put got " + i);
            return;
        }
    }

    public class ThreadParms
    {
        public int ManagedThreadId { get; set; }
        public ServiceEndpoint ServiceEndpoint { get; set; }
    }

    public class BenchmarkService
    {
        public readonly int ThreadCount;
        public readonly int ThreadCallCount;
        public readonly Type ServiceType; 

        int _completed = 0;
        System.Diagnostics.Stopwatch _stopWatch;
        EventWaitHandle _waitHandle;
        bool _done;

        public BenchmarkService(Type serviceType, int threadCount, int threadCallCount)
        {
            this.ServiceType = serviceType;
            this.ThreadCount = threadCount;
            this.ThreadCallCount = threadCallCount;

            _done = false;
        }

        public void Run(string baseAddress)
        {
            if (_done)
                throw new InvalidOperationException("Can't run twice");

            ServiceHost host = new ServiceHost(ServiceType, new Uri(baseAddress));
            host.Open();

            Console.WriteLine("Starting " + ServiceType.Name + " on " + baseAddress + "...");

            _waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
            _completed = 0;
            _stopWatch = System.Diagnostics.Stopwatch.StartNew();

            ServiceEndpoint endpoint = host.Description.Endpoints.Find(typeof(ISimple));

            for (int i = 1; i <= ThreadCount; i++)
            {
                // ServiceEndpoint is NOT thread safe. Make a copy for each thread.
                ServiceEndpoint temp = new ServiceEndpoint(endpoint.Contract, endpoint.Binding, endpoint.Address);
                ThreadPool.QueueUserWorkItem(new WaitCallback(CallServiceManyTimes),
                    new ThreadParms() { ManagedThreadId = i, ServiceEndpoint = temp });
            }

            _waitHandle.WaitOne();
            host.Shutdown();

            _done = true;

            //Console.WriteLine("All DONE.");
            Console.WriteLine("    Type=" + ServiceType.Name + "  ThreadCount=" + ThreadCount + "  ThreadCallCount=" + ThreadCallCount);
            Console.WriteLine("    runtime: " + _stopWatch.ElapsedTicks + " ticks   " + _stopWatch.ElapsedMilliseconds + " msec");
        }

        public void CallServiceManyTimes(object threadParams)
        {
            ThreadParms p = (ThreadParms)threadParams;

            ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(p.ServiceEndpoint);
            ISimple proxy = factory.CreateChannel();

            for (int i = 1; i < ThreadCallCount; i++)
            {
                proxy.Put();
            }

            ((ICommunicationObject)proxy).Shutdown();
            factory.Shutdown();

            int currentCompleted = Interlocked.Increment(ref _completed);

            if (currentCompleted == ThreadCount)
            {
                _stopWatch.Stop();
                _waitHandle.Set();
            }
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            BenchmarkService benchmark;
            int threadCount = 600;
            int threadCalls = 500;
            string baseAddress = "net.pipe://localhost/base";

            for (int i = 0; i <= 4; i++)
            {
                benchmark = new BenchmarkService(typeof(SingleService), threadCount, threadCalls);
                benchmark.Run(baseAddress);

                benchmark = new BenchmarkService(typeof(MultipleService), threadCount, threadCalls);
                benchmark.Run(baseAddress);
            }

            baseAddress = "http://localhost/base";

            for (int i = 0; i <= 4; i++)
            {
                benchmark = new BenchmarkService(typeof(SingleService), threadCount, threadCalls);
                benchmark.Run(baseAddress);

                benchmark = new BenchmarkService(typeof(MultipleService), threadCount, threadCalls);
                benchmark.Run(baseAddress);
            }

            Console.WriteLine("Press ENTER to close.");
            Console.ReadLine();

        }
    }

    public static class Extensions
    {
        static public void Shutdown(this ICommunicationObject obj)
        {
            try
            {
                if (obj != null)
                    obj.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Shutdown exception: {0}", ex.Message);
                obj.Abort();
            }
        }
    }
}

I always thought that setting InstanceContextMode to PerCall makes concurrency mode irrelevant even if using a session aware binding like net.tcp. This is what MSDN says http://msdn.microsoft.com/en-us/library/ms731193.aspx "In PerCallinstancing, concurrency is not relevant, because each message is processed by a new InstanceContext and, therefore, never more than one thread is active in the InstanceContext."


But today I was going through Juval Lowy's book Programming WCF Services and he writes in Chapter 8

If the per-call service has a transport-level session, whether concurrent processing of calls is allowed is a product of the service concurrency mode. If the service is configured with ConcurrencyMode.Single, concurrent processing of the pending calls is not al lowed, and the calls are dispatched one at a time. [...] I consider this to be a flawed design. If the service is configured with ConcurrencyMode.Multiple, concurrent pro- cessing is allowed. Calls are dispatched as they arrive, each to a new instance, and execute concurrently. An interesting observation here is that in the interest of through- put, it is a good idea to configure a per-call service with ConcurrencyMode.Multiple— the instance itself will still be thread-safe (so you will not incur the synchronization liability), yet you will allow concurrent calls from the same client.


This is contradicting my understanding and what MSDN says. Which is correct ? In my case I have a WCF Net.Tcp service used my many client applications that creates a new proxy object, makes the call and then immediately closes the proxy. The service has PerCall InstanceContextMode. Will I get improved throughput if I change the InstanceContextMode to Multiple with no worse thread safety behaviour than percall ?

解决方案

The key phrase in reading Lowy’s statement is "in the interest of throughput". Lowy is pointing out that when using ConcurrencyMode.Single WCF will blindly implement a lock to enforce serialization to the service instance. Locks are expensive and this one isn’t necessary because PerCall already guarantees that a second thread will never try to call the same service instance.

In terms of behavior: ConcurrencyMode does not matter for a PerCall service instance.

In terms of performance: A PerCall service that is ConcurrencyMode.Multiple should be slightly faster because its not creating and acquiring the (unneeded) thread lock that ConcurrencyMode.Single is using.

I wrote a quick benchmark program to see if I could measure the performance impact of Single vs Multiple for a PerCall service: The benchmark showed no meaningful difference.

I pasted in the code below if you want to try running it yourself.

Test cases I tried:

  • 600 threads calling a service 500 times
  • 200 threads calling a service 1000 times
  • 8 threads calling a service 10000 times
  • 1 thread calling a service 10000 times

I ran this on a 4 CPU VM running Service 2008 R2. All but the 1 thread case was CPU constrained.

Results: All the runs were within about 5% of eachother. Sometimes ConcurrencyMode.Multiple was faster. Sometimes ConcurrencyMode.Single was faster. Maybe a proper statistical analysis could pick a winner. In my opinion they are close enough to not matter.

Here’s a typical output:

Starting Single Service on net.pipe://localhost/base... Type=SingleService ThreadCount=600 ThreadCallCount=500 runtime: 45156759 ticks 12615 msec

Starting Multiple Service on net.pipe://localhost/base... Type=MultipleService ThreadCount=600 ThreadCallCount=500 runtime: 48731273 ticks 13613 msec

Starting Single Service on net.pipe://localhost/base... Type=SingleService ThreadCount=600 ThreadCallCount=500 runtime: 48701509 ticks 13605 msec

Starting Multiple Service on net.pipe://localhost/base... Type=MultipleService ThreadCount=600 ThreadCallCount=500 runtime: 48590336 ticks 13574 msec

Benchmark Code:

Usual caveat: This is benchmark code that takes short cuts that aren’t appropriate for production use.

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WCFTest
{
    [ServiceContract]
    public interface ISimple
    {
        [OperationContract()]
        void Put();
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
    public class SingleService : ISimple
    {
        public void Put()
        {
            //Console.WriteLine("put got " + i);
            return;
        }
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class MultipleService : ISimple
    {
        public void Put()
        {
            //Console.WriteLine("put got " + i);
            return;
        }
    }

    public class ThreadParms
    {
        public int ManagedThreadId { get; set; }
        public ServiceEndpoint ServiceEndpoint { get; set; }
    }

    public class BenchmarkService
    {
        public readonly int ThreadCount;
        public readonly int ThreadCallCount;
        public readonly Type ServiceType; 

        int _completed = 0;
        System.Diagnostics.Stopwatch _stopWatch;
        EventWaitHandle _waitHandle;
        bool _done;

        public BenchmarkService(Type serviceType, int threadCount, int threadCallCount)
        {
            this.ServiceType = serviceType;
            this.ThreadCount = threadCount;
            this.ThreadCallCount = threadCallCount;

            _done = false;
        }

        public void Run(string baseAddress)
        {
            if (_done)
                throw new InvalidOperationException("Can't run twice");

            ServiceHost host = new ServiceHost(ServiceType, new Uri(baseAddress));
            host.Open();

            Console.WriteLine("Starting " + ServiceType.Name + " on " + baseAddress + "...");

            _waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
            _completed = 0;
            _stopWatch = System.Diagnostics.Stopwatch.StartNew();

            ServiceEndpoint endpoint = host.Description.Endpoints.Find(typeof(ISimple));

            for (int i = 1; i <= ThreadCount; i++)
            {
                // ServiceEndpoint is NOT thread safe. Make a copy for each thread.
                ServiceEndpoint temp = new ServiceEndpoint(endpoint.Contract, endpoint.Binding, endpoint.Address);
                ThreadPool.QueueUserWorkItem(new WaitCallback(CallServiceManyTimes),
                    new ThreadParms() { ManagedThreadId = i, ServiceEndpoint = temp });
            }

            _waitHandle.WaitOne();
            host.Shutdown();

            _done = true;

            //Console.WriteLine("All DONE.");
            Console.WriteLine("    Type=" + ServiceType.Name + "  ThreadCount=" + ThreadCount + "  ThreadCallCount=" + ThreadCallCount);
            Console.WriteLine("    runtime: " + _stopWatch.ElapsedTicks + " ticks   " + _stopWatch.ElapsedMilliseconds + " msec");
        }

        public void CallServiceManyTimes(object threadParams)
        {
            ThreadParms p = (ThreadParms)threadParams;

            ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(p.ServiceEndpoint);
            ISimple proxy = factory.CreateChannel();

            for (int i = 1; i < ThreadCallCount; i++)
            {
                proxy.Put();
            }

            ((ICommunicationObject)proxy).Shutdown();
            factory.Shutdown();

            int currentCompleted = Interlocked.Increment(ref _completed);

            if (currentCompleted == ThreadCount)
            {
                _stopWatch.Stop();
                _waitHandle.Set();
            }
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            BenchmarkService benchmark;
            int threadCount = 600;
            int threadCalls = 500;
            string baseAddress = "net.pipe://localhost/base";

            for (int i = 0; i <= 4; i++)
            {
                benchmark = new BenchmarkService(typeof(SingleService), threadCount, threadCalls);
                benchmark.Run(baseAddress);

                benchmark = new BenchmarkService(typeof(MultipleService), threadCount, threadCalls);
                benchmark.Run(baseAddress);
            }

            baseAddress = "http://localhost/base";

            for (int i = 0; i <= 4; i++)
            {
                benchmark = new BenchmarkService(typeof(SingleService), threadCount, threadCalls);
                benchmark.Run(baseAddress);

                benchmark = new BenchmarkService(typeof(MultipleService), threadCount, threadCalls);
                benchmark.Run(baseAddress);
            }

            Console.WriteLine("Press ENTER to close.");
            Console.ReadLine();

        }
    }

    public static class Extensions
    {
        static public void Shutdown(this ICommunicationObject obj)
        {
            try
            {
                if (obj != null)
                    obj.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Shutdown exception: {0}", ex.Message);
                obj.Abort();
            }
        }
    }
}

这篇关于当InstanceContextMode为具有Net.Tcp绑定的WCF服务的PerCall时,Multiple的ConcurrencyMode是否具有相关性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆