【问题标题】:Does ConcurrencyMode of Multiple have relevance when InstanceContextMode is PerCall for a WCF service with Net.Tcp binding?对于具有 Net.Tcp 绑定的 WCF 服务,当 InstanceContextMode 为 PerCall 时,Multiple 的 ConcurrencyMode 是否具有相关性?
【发布时间】:2012-09-01 10:41:55
【问题描述】:

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


但今天我正在阅读 Juval Lowy 的书 Programming WCF Services,他在第 8 章中写道

如果每次调用服务具有传输级会话,是否 允许并发处理呼叫是服务的产物 并发模式。如果服务配置为 ConcurrencyMode.Single,待处理的并发处理 不允许调用,并且调用一次调度一个。 [...] 我认为这是一个有缺陷的设计。如果服务是 配置了 ConcurrencyMode.Multiple,并发处理是 允许。调用在它们到达时被分派,每个调用到一个新实例, 并同时执行。这里一个有趣的观察是,在 考虑到吞吐量,最好配置一个 使用 ConcurrencyMode.Multiple 的每次调用服务——实例本身 仍然是线程安全的(所以你不会引起同步 责任),但您将允许来自同一客户端的并发调用。


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

【问题讨论】:

  • 很好的问题。您是否考虑过在控制台应用程序中构建它并对其进行测试以查看?

标签: multithreading wcf


【解决方案1】:

阅读 Lowy 声明的关键词是“in the interest of throughput”。 Lowy 指出,在使用 ConcurrencyMode.Single 时,WCF 会盲目地实现一个锁来强制对服务实例进行序列化。锁很昂贵,而且这个不是必需的,因为 PerCall 已经保证第二个线程永远不会尝试调用同一个服务实例。

在行为方面: ConcurrencyMode 与 PerCall 服务实例无关。

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

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

如果你想尝试自己运行它,我粘贴了下面的代码。

我试过的测试用例:

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

我在一个运行 Service 2008 R2 的 4 CPU 虚拟机上运行它。除了 1 个线程之外的所有情况都受到 CPU 限制。

结果: 所有的运行都在大约 5% 的范围内。 有时 ConcurrencyMode.Multiple 更快。有时 ConcurrencyMode.Single 更快。也许适当的统计分析可以选出赢家。在我看来,它们足够接近,无关紧要。

这是一个典型的输出:

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

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

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

在 net.pipe://localhost/base 上启动 Multiple 服务... 类型=MultipleService 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();
            }
        }
    }
}

【讨论】:

  • 谢谢,这证实了我的想法。至少将 ConcurrencyMode 设置为 Multiple 并没有什么坏处。
猜你喜欢
  • 2011-10-30
  • 1970-01-01
  • 2015-01-12
  • 1970-01-01
  • 1970-01-01
  • 2019-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多