10

私は常に、InstanceContextModeをPerCallに設定すると、net.tcpのようなセッション対応のバインディングを使用している場合でも、同時実行モードは無関係になると考えていました。これは、MSDNがhttp://msdn.microsoft.com/en-us/library/ms731193.aspxと言っていることです 。 「PerCallinstancingでは、各メッセージは新しいInstanceContextによって処理されるため、同時実行性は関係ありません。スレッドはInstanceContextでアクティブです。」


しかし、今日私はJuvalLowyの本ProgrammingWCF Servicesを読んでいて、彼は第8章に書いています。

コールごとのサービスにトランスポートレベルのセッションがある場合、コールの同時処理が許可されるかどうかは、サービスの同時実行モードの結果です。サービスがConcurrencyMode.Singleで構成されている場合、保留中の呼び出しの同時処理は許可されず、呼び出しは一度に1つずつディスパッチされます。[...]これは欠陥のあるデザインだと思います。サービスがConcurrencyMode.Multipleで構成されている場合、同時処理が許可されます。呼び出しは、到着時にそれぞれ新しいインスタンスにディスパッチされ、同時に実行されます。ここでの興味深い観察は、スループットの観点から、ConcurrencyMode.Multipleを使用して呼び出しごとのサービスを構成することをお勧めします。インスタンス自体は引き続きスレッドセーフです(したがって、同期の責任は発生しません)。 、


これは私の理解とMSDNの言うことと矛盾しています。どちらが正しい ?私の場合、WCF Net.Tcpサービスで、新しいプロキシオブジェクトを作成し、呼び出しを行ってすぐにプロキシを閉じる多くのクライアントアプリケーションを使用しています。サービスにはPerCallInstanceContextModeがあります。InstanceContextModeをMultipleに変更し、percallよりもスレッドセーフの動作が悪くならない場合、スループットが向上しますか?

4

1 に答える 1

8

Lowyの声明を読む際のキーワードは、「スループットのために<em>」です。Lowyは、ConcurrencyMode.Single WCFを使用すると、サービスインスタンスへのシリアル化を強制するために、盲目的にロックを実装することを指摘しています。ロックは高価であり、PerCallは、2番目のスレッドが同じサービスインスタンスを呼び出そうとしないことをすでに保証しているため、これは必要ありません。

動作に関して: ConcurrencyModeは、PerCallサービスインスタンスでは重要ではありません。

パフォーマンスの観点から: ConcurrencyMode.MultipleであるPerCallサービスは、ConcurrencyMode.Singleが使用している(不要な)スレッドロックを作成および取得しないため、わずかに高速になるはずです。

PerCallサービスのシングルとマルチプルのパフォーマンスへの影響を測定できるかどうかを確認するための簡単なベンチマークプログラムを作成しました。ベンチマークには意味のある違いはありませんでした。

自分で実行してみたい場合は、以下のコードを貼り付けました。

私が試したテストケース:

  • 600スレッドがサービスを500回呼び出す
  • サービスを1000回呼び出す200スレッド
  • サービスを10000回呼び出す8つのスレッド
  • 1つのスレッドが10000回サービスを呼び出す

これは、Service2008R2を実行している4CPUVMで実行しました。1スレッドの場合を除いて、すべてCPUに制約がありました。

結果:すべての実行は、互いに約5%以内でした。 ConcurrencyMode.Multipleの方が速い場合がありました。ConcurrencyMode.Singleの方が速い場合がありました。たぶん、適切な統計分析が勝者を選ぶかもしれません。私の意見では、それらは問題にならないほど十分に近いです。

典型的な出力は次のとおりです。

net.pipe:// localhost/baseでシングルサービスを開始しています...Type= SingleService ThreadCount = 600 ThreadCallCount = 500 ランタイム:45156759ティック 12615ミリ秒

net.pipe:// localhost/baseで 複数のサービスを開始しています...Type= MultipleService ThreadCount = 600 ThreadCallCount = 500ランタイム:48731273ティック 13613ミリ秒

net.pipe:// localhost/baseでシングルサービスを開始しています...Type= SingleService ThreadCount = 600 ThreadCallCount = 500 ランタイム:48701509ティック 13605ミリ秒

net.pipe:// localhost/baseで 複数のサービスを開始しています...Type= 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();
            }
        }
    }
}
于 2012-09-06T15:53:31.807 に答える