다음을 통해 공유


풀링

풀링샘플은 개체 풀링을 지원하도록 WCF(Windows Communication Foundation)를 확장하는 방법을 보여 줍니다. 엔터프라이즈 서비스의 ObjectPoolingAttribute 특성과 구문 및 의미 체계가 비슷한 특성을 만드는 방법을 소개합니다. 개체 풀링을 통해 애플리케이션의 성능을 크게 높일 수 있습니다. 그러나 제대로 사용하지 않으면 역효과가 일어나기도 합니다. 개체 풀링은 광범위한 초기화가 필요하며 자주 사용되는 개체를 다시 만드는 오버헤드를 줄이는 데 도움이 됩니다. 그러나 풀링된 개체에서 메서드 호출이 완료되기까지 상당한 시간이 걸리는 경우 최대 풀 크기에 도달하는 즉시 개체 풀링은 추가 요청을 큐에 보냅니다. 따라서 일부 개체 만들기 요청을 처리하지 않고 시간 초과 예외를 throw할 수 있습니다.

참고 항목

이 샘플의 설치 절차 및 빌드 지침은 이 항목의 끝부분에 나와 있습니다.

확장을 만들려면 먼저 사용할 WCF 확장명 지점을 결정해야 합니다.

WCF에서 디스패처라는 용어는 들어오는 메시지를 사용자 서비스의 메서드 호출로 변환하고 그 메서드의 반환 값을 보내는 메시지로 변환하는 런타임 구성 요소를 말합니다. WCF 서비스는 각 엔드포인트에 대해 디스패처를 만듭니다. WCF 클라이언트는 그 클라이언트와 연결된 계약이 이중 계약인 경우 디스패처를 사용해야 합니다.

채널 및 엔드포인트 디스패처는 디스패처의 동작을 제어하는 다양한 속성을 노출시켜 채널 및 계약 수준의 확장성을 제공합니다. 또한 DispatchRuntime 속성을 사용하면 디스패치 프로세스를 검사, 수정하거나 사용자 지정할 수 있습니다. 이 샘플에서는 서비스 클래스의 인스턴스를 제공하는 개체를 가리키는 InstanceProvider 속성을 중점적으로 다룹니다.

IInstanceProvider

WCF에서는 디스패처가 IInstanceProvider 인터페이스를 구현하는 InstanceProvider를 사용하여 서비스 클래스의 인스턴스를 만듭니다. 이 인터페이스에는 세 개의 메서드가 있습니다.

개체 풀

사용자 지정 IInstanceProvider 구현에서 서비스에 필요한 개체 풀링 의미 체계를 제공합니다. 따라서 이 샘플에는 풀링을 위한 사용자 지정 ObjectPoolingInstanceProvider 구현을 제공하는 IInstanceProvider 형식이 있습니다. Dispatcher가 새 인스턴스를 만들지 않고 GetInstance(InstanceContext, Message) 메서드를 호출할 때 사용자 지정 구현은 메모리에 있는 풀에서 기존 개체를 찾습니다. 사용할 수 있는 개체가 있으면 반환되고, 그렇지 않으면 새 개체가 만들어집니다. GetInstance의 구현은 다음 샘플 코드에 표시되어 있습니다.

object IInstanceProvider.GetInstance(InstanceContext instanceContext, Message message)
{
    object obj = null;

    lock (poolLock)
    {
        if (pool.Count > 0)
        {
            obj = pool.Pop();
        }
        else
        {
            obj = CreateNewPoolObject();
        }
        activeObjectsCount++;
    }

    WritePoolMessage(ResourceHelper.GetString("MsgNewObject"));

    idleTimer.Stop();

    return obj;
}

사용자 지정 ReleaseInstance 구현에서는 해제된 인스턴스를 다시 풀에 추가하고 ActiveObjectsCount 값을 감소시킵니다. Dispatcher는 서로 다른 스레드에 있는 이 메서드를 호출할 수 있기 때문에 ObjectPoolingInstanceProvider 클래스에서 클래스 수준 멤버에 대한 액세스의 동기화가 필요합니다.

void IInstanceProvider.ReleaseInstance(InstanceContext instanceContext, object instance)
{
    lock (poolLock)
    {
        pool.Push(instance);
        activeObjectsCount--;

        WritePoolMessage(
        ResourceHelper.GetString("MsgObjectPooled"));

        // When the service goes completely idle (no requests
        // are being processed), the idle timer is started
        if (activeObjectsCount == 0)
            idleTimer.Start();
    }
}

ReleaseInstance 메서드는 정리 초기화 기능을 제공합니다. 일반적으로 풀은 풀의 수명 동안 최소한의 개체 수를 유지합니다. 하지만 사용량이 지나치게 많은 경우에는 구성에 지정된 최대 한도를 사용하기 위해 풀에 추가 개체를 만들어야 할 수도 있습니다. 결국 풀의 사용이 줄어들면 그런 여분의 개체가 추가 오버헤드가 될 수 있습니다. 따라서 activeObjectsCount가 0에 도달하면 유휴 타이머가 시작되고 정리 주기를 트리거 및 수행합니다.

동작 추가

디스패처 계층 확장은 다음 동작을 사용하여 후크합니다.

  • 서비스 동작. 전체 서비스 런타임을 사용자 지정할 수 있습니다.

  • 엔드포인트 동작. 서비스 엔드포인트, 특히 채널 및 엔드포인트 디스패처를 사용자 지정할 수 있습니다.

  • 계약 동작. 클라이언트 및 서비스에서 각각 ClientRuntimeDispatchRuntime 클래스를 모두 사용자 지정할 수 있습니다.

개체 풀링 확장을 위해 서비스 동작을 만들어야 합니다. 서비스 동작은 IServiceBehavior 인터페이스를 구현하여 만듭니다. 서비스 모델에 사용자 지정 동작을 인식시키는 방법에는 몇 가지가 있습니다.

  • 사용자 지정 특성 사용

  • 사용자 지정 동작을 서비스 설명의 동작 컬렉션에 명령적으로 추가

  • 구성 파일 확장

이 샘플에서는 사용자 지정 특성을 사용합니다. ServiceHost가 생성되면 이는 서비스의 형식 정의에 사용된 특성을 확인하고 사용 가능한 동작을 서비스 설명의 동작 컬렉션에 추가합니다.

IServiceBehavior 인터페이스에는 세 개의 메서드, 즉 Validate, AddBindingParametersApplyDispatchBehavior가 있습니다. Validate 메서드는 동작이 서비스에 적용될 수 있음을 확인할 때 사용합니다. 이 샘플의 구현에서는 서비스에 Single이 구현되지 않음을 확인합니다. AddBindingParameters 메서드는 서비스의 바인딩을 구성하는 데 사용되며, 이 시나리오에서는 필요하지 않습니다. ApplyDispatchBehavior는 서비스의 디스패처를 구성하는 데 사용됩니다. ServiceHost가 초기화 중일 때 WCF에서 이 메서드를 호출합니다. 다음 매개 변수가 이 메서드로 전달됩니다.

  • Description: 이 인수는 전체 서비스의 서비스 설명을 제공합니다. 서비스의 엔드포인트, 계약, 바인딩 및 기타 데이터에 대한 설명을 검사할 때 사용할 수 있습니다.

  • ServiceHostBase: 이 인수는 현재 초기화되는 ServiceHostBase를 제공합니다.

사용자 지정 IServiceBehavior 구현에서는 ObjectPoolingInstanceProvider의 새 인스턴스가 인스턴스화되어 ServiceHostBase에서 각 InstanceProviderDispatchRuntime 속성에 할당됩니다.

void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
    // Create an instance of the ObjectPoolInstanceProvider.
    ObjectPoolingInstanceProvider instanceProvider = new
           ObjectPoolingInstanceProvider(description.ServiceType,
                                                    minPoolSize);

    // Forward the call if we created a ServiceThrottlingBehavior.
    if (this.throttlingBehavior != null)
    {
        ((IServiceBehavior)this.throttlingBehavior).ApplyDispatchBehavior(description, serviceHostBase);
    }

    // In case there was already a ServiceThrottlingBehavior
    // (this.throttlingBehavior==null), it should have initialized
    // a single ServiceThrottle on all ChannelDispatchers.
    // As we loop through the ChannelDispatchers, we verify that
    // and modify the ServiceThrottle to guard MaxPoolSize.
    ServiceThrottle throttle = null;

    foreach (ChannelDispatcherBase cdb in
            serviceHostBase.ChannelDispatchers)
    {
        ChannelDispatcher cd = cdb as ChannelDispatcher;
        if (cd != null)
        {
            // Make sure there is exactly one throttle used by all
            // endpoints. If there were others, we could not enforce
            // MaxPoolSize.
            if ((this.throttlingBehavior == null) &&
                        (this.maxPoolSize != Int32.MaxValue))
            {
                throttle ??= cd.ServiceThrottle;
                if (cd.ServiceThrottle == null)
                {
                    throw new
InvalidOperationException(ResourceHelper.GetString("ExNullThrottle"));
                }
                if (throttle != cd.ServiceThrottle)
                {
                    throw new InvalidOperationException(ResourceHelper.GetString("ExDifferentThrottle"));
                }
             }

             foreach (EndpointDispatcher ed in cd.Endpoints)
             {
                 // Assign it to DispatchBehavior in each endpoint.
                 ed.DispatchRuntime.InstanceProvider =
                                      instanceProvider;
             }
         }
     }

     // Set the MaxConcurrentInstances to limit the number of items
     // that will ever be requested from the pool.
     if ((throttle != null) && (throttle.MaxConcurrentInstances >
                                      this.maxPoolSize))
     {
         throttle.MaxConcurrentInstances = this.maxPoolSize;
     }
}

IServiceBehavior 구현 외에 ObjectPoolingAttribute 클래스에도 특성 인수를 사용하여 개체 풀을 사용자 지정하는 멤버가 몇 개 있습니다. 이 멤버에는 .NET Enterprise Services에서 제공되는 개체 풀링 기능 집합과 일치하는 MaxPoolSize, MinPoolSizeCreationTimeout이 포함됩니다.

이제 새로 만든 사용자 지정 ObjectPooling 특성으로 서비스 구현을 주석으로 지정하여 개체 풀링 동작을 서비스에 추가할 수 있습니다.

[ObjectPooling(MaxPoolSize=1024, MinPoolSize=10, CreationTimeout=30000)]
public class PoolService : IPoolService
{
  // …
}

샘플 실행

이 샘플에서는 특정 시나리오에서 개체 풀링을 사용할 경우 얻을 수 있는 성능상의 이점을 보여 줍니다.

서비스 애플리케이션은 WorkServiceObjectPooledWorkService의 두 가지 서비스를 구현합니다. 두 서비스 모두 동일한 구현을 공유합니다. 둘 다 고비용의 초기화가 필요하며 그 다음에는 상대적으로 경제적인 DoWork() 메서드를 노출합니다. 유일한 차이점은 ObjectPooledWorkService에 개체 풀링이 구성된다는 것입니다.

[ObjectPooling(MinPoolSize = 0, MaxPoolSize = 5)]
public class ObjectPooledWorkService : IDoWork
{
    public ObjectPooledWorkService()
    {
        Thread.Sleep(5000);
        ColorConsole.WriteLine(ConsoleColor.Blue, "ObjectPooledWorkService instance created.");
    }

    public void DoWork()
    {
        ColorConsole.WriteLine(ConsoleColor.Blue, "ObjectPooledWorkService.GetData() completed.");
    }
}

클라이언트를 실행하면 WorkService를 5회 호출하여 시간을 확인합니다. 그런 다음 ObjectPooledWorkService를 5회 호출하여 시간을 확인합니다. 그리고 나서 시간의 차이를 표시합니다.

Press <ENTER> to start the client.

Calling WorkService:
1 - DoWork() Done
2 - DoWork() Done
3 - DoWork() Done
4 - DoWork() Done
5 - DoWork() Done
Calling WorkService took: 26722 ms.
Calling ObjectPooledWorkService:
1 - DoWork() Done
2 - DoWork() Done
3 - DoWork() Done
4 - DoWork() Done
5 - DoWork() Done
Calling ObjectPooledWorkService took: 5323 ms.
Press <ENTER> to exit.

참고 항목

클라이언트가 맨 처음 실행되면 두 서비스에 소요되는 시간이 대략 같은 것 같습니다. 샘플을 다시 실행하면 ObjectPooledWorkService가 훨씬 빨리 반환되는 것을 알 수 있는데, 이 개체의 인스턴스가 이미 풀에 존재하기 때문입니다.

샘플을 설치, 빌드 및 실행하려면

  1. Windows Communication Foundation 샘플의 일회 설치 절차를 수행했는지 확인합니다.

  2. 솔루션을 빌드하려면 Windows Communication Foundation 샘플 빌드의 지침을 따릅니다.

  3. 단일 컴퓨터 또는 다중 컴퓨터 구성에서 샘플을 실행하려면 Windows Communication Foundation 샘플 실행의 지침을 따릅니다.

참고 항목

Svcutil.exe를 사용하여 이 샘플에 대한 구성을 다시 생성할 경우 클라이언트 구성에서 엔드포인트 이름을 클라이언트 코드와 일치하도록 수정해야 합니다.