Does ConcurrencyMode of Multiple have relevance when InstanceContextMode is PerCall for a WCF service with Net.Tcp binding?

前端 未结 1 1576
栀梦
栀梦 2021-01-11 17:35

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 h

1条回答
  •  挽巷
    挽巷 (楼主)
    2021-01-11 17:57

    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 factory = new ChannelFactory(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();
                }
            }
        }
    }
    

    0 讨论(0)
提交回复
热议问题