Имеет ли ConcurrencyMode Multiple релевантность, когда InstanceContextMode является PerCall для службы WCF с привязкой Net.Tcp?

Я всегда думал, что установка InstanceContextMode в PerCall делает режим concurrency нерелевантным, даже если используется привязка к сеансу, такая как net.tcp. Это то, что MSDN говорит http://msdn.microsoft.com/en-us/library/ms731193.aspx "В PerCallinstancing, concurrency не имеет значения, потому что каждое сообщение обрабатывается новым экземпляром InstanceContext, и поэтому в экземпляре-экземпляре активен не более одного потока."


Но сегодня я проходил через книгу Juval Lowy Программирование WCF Services, и он пишет в главе 8

Если служба для каждого вызова имеет сеанс транспортного уровня, одновременная обработка вызовов разрешена, является продуктом услуги concurrency. Если служба настроена с помощью ConcurrencyMode.Single, одновременная обработка ожидающих звонки не снижаются, а вызовы отправляются по одному. [...] Я считаю, что это ошибочный дизайн. Если услуга сконфигурирован с ConcurrencyMode.Multiple, одновременная обработка позволил. Вызовы отправляются по мере их поступления, каждый в новый экземпляр, и выполнять одновременно. Интересное замечание здесь состоит в том, что в заинтересованность в переходе, рекомендуется настроить услуга за вызов с ConcurrencyMode.Multiple - сам экземпляр будет по-прежнему быть потокобезопасным (так что вы не понесете синхронизацию ответственность), но вы разрешите одновременные вызовы от одного и того же клиента.


Это противоречит моему пониманию и тому, что говорит MSDN. Что правильно? В моем случае у меня есть служба WCF Net.Tcp, которая использовала мои многочисленные клиентские приложения, которые создают новый прокси-объект, выполняет вызов, а затем немедленно закрывает прокси-сервер. Услуга имеет PerCall InstanceContextMode. Получу ли я улучшенную пропускную способность, если я изменил InstanceContextMode на Multiple, не хуже, чем на percall?

Ответ 1

Ключевая фраза при чтении оператора Lowys - "в интересах пропускной способности". Лоуи указывает, что при использовании ConcurrencyMode.Single WCF будет слепо реализовывать блокировку, чтобы обеспечить сериализацию экземпляра службы. Замки стоят дорого, и это не обязательно, потому что PerCall уже гарантирует, что второй поток никогда не попытается вызвать тот же экземпляр службы.

С точки зрения поведения: ConcurrencyMode не имеет значения для экземпляра службы PerCall.

С точки зрения производительности: Служба PerCall, которая является ConcurrencyMode.Multiple должна быть немного быстрее, потому что она не создает и не захватывает (ненужный) поток, который использует ConcurrencyMode.Single.

Я написал программу быстрого тестирования, чтобы увидеть, могу ли я измерить влияние производительности Single vs Multiple для службы PerCall: Тест не показал значимой разницы.

Я вставил код ниже, если вы хотите попробовать запустить его самостоятельно.

Тесты, которые я пробовал:

  • 600 потоков, вызывающих службу 500 раз
  • 200 потоков, вызывающих службу 1000 раз
  • 8 потоков, вызывающих службу 10000 раз
  • 1 поток, вызывающий службу 10000 раз

Я запустил это на 4-процессорной VM, на которой запущен Service 2008 R2. Все, кроме 1 потока, были ограничены процессором.

Результаты: Все прогоны находились в пределах примерно 5% друг от друга. Иногда ConcurrencyMode.Multiple был быстрее. Иногда ConcurrencyMode.Single был быстрее. Возможно, правильный статистический анализ мог бы выбрать победителя. По-моему, они достаточно близки, чтобы не иметь значения.

Существует типичный вывод:

Запуск Single Сервис на net.pipe://localhost/base...   Тип = SingleService ThreadCount = 600 ThreadCallCount = 500    Время выполнения: 45156759 тиков 12615 мсек

Запуск Несколько Сервис на net.pipe://localhost/base...   Тип = MultipleService ThreadCount = 600 ThreadCallCount = 500    время выполнения: 48731273 тики 13613 мсек

Запуск Single Сервис на net.pipe://localhost/base...   Тип = SingleService ThreadCount = 600 ThreadCallCount = 500    Время выполнения: 48701509 тиков 13605 мсек

Запуск Несколько Сервис на net.pipe://localhost/base...   Тип = 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();
            }
        }
    }
}