天天看點

WCF - 服務執行個體管理模式

WCF 提供了三種執行個體上下文模式:PreCall、PreSession 以及 Single。開發人員通過 ServiceBehavior.InstanceContextMode 就可以很容易地控制服務對象的執行個體管理模式。而當 WCF 釋放服務對象時,會檢查該對象是否實作了 IDisposable 接口,并調用其 Dispose 方法,以便及時釋放相關資源,同時也便于我們觀察對象釋放行為。 1. PreCall 在 PreCall 模式下,即便使用同一個代理對象,也會為每次調用建立一個服務執行個體。調用結束後,服務執行個體被立即釋放(非垃圾回收)。對于不支援 Session 的 Binding,如 BasicHttpBinding,其預設行為就是 PreCall。

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void Test();
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyServie : IMyService, IDisposable
{
    public MyServie()
    {
        Console.WriteLine("Constructor:{0}", this.GetHashCode());
    }

    [OperationBehavior]
    public void Test()
    {
        Console.WriteLine("Test:{0}", OperationContext.Current.SessionId);
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose");
    }
}

public class WcfTest
{
    public static void Test()
    {
        AppDomain.CreateDomain("Server").DoCallBack(delegate
        {
            ServiceHost host = new ServiceHost(typeof(MyServie), new Uri("http://localhost:8080/MyService"));
            host.AddServiceEndpoint(typeof(IMyService), new WSHttpBinding(), "");
            host.Open();
        });

        //-----------------------

        IMyService channel = ChannelFactory<IMyService>.CreateChannel(new WSHttpBinding(),
            new EndpointAddress("http://localhost:8080/MyService"));

        using (channel as IDisposable)
        {
            channel.Test();
            channel.Test();
        }
    }
}      

輸出:

Constructor:30136159

Test:urn:uuid:df549447-52ba-4c54-9432-31a7a533d9b4

Dispose

Constructor:41153804

2. PreSession

PreSession 模式需要綁定到支援 Session 的 Binding 對象。在用戶端代理觸發終止操作前,WCF 為每個用戶端維持同一個服務對象,是以 PreSession 模式可用來保持調用狀态。也正因為如此,PreSession 在大并發服務上使用時要非常小心,避免造成伺服器過度負擔。雖然支援 Session 的 Binding 對象預設就會啟用 PreSession 模式,但依然建議你強制指定 SessionMode.Required 和 InstanceContextMode.PerSession。

[ServiceContract(SessionMode = SessionMode.Required)]
public interface IMyService
{
    [OperationContract]
    void Test();
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyServie : IMyService, IDisposable
{
    public MyServie()
    {
        Console.WriteLine("Constructor:{0}", this.GetHashCode());
    }

    [OperationBehavior]
    public void Test()
    {
        Console.WriteLine("Test:{0}", OperationContext.Current.SessionId);
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose");
    }
}

public class WcfTest
{
    public static void Test()
    {
        AppDomain.CreateDomain("Server").DoCallBack(delegate
        {
            ServiceHost host = new ServiceHost(typeof(MyServie), new Uri("http://localhost:8080/MyService"));
            host.AddServiceEndpoint(typeof(IMyService), new WSHttpBinding(), "");
            host.Open();
        });

        //-----------------------

        IMyService channel = ChannelFactory<IMyService>.CreateChannel(new WSHttpBinding(), 
            new EndpointAddress("http://localhost:8080/MyService"));
            
        using (channel as IDisposable)
        {
            channel.Test();
            channel.Test();
        }
    }
}      

Test:urn:uuid:2f01b61d-40c6-4f1b-a4d6-4f4bc3e8847a

3. Single

一如其名,伺服器會在啟動時,建立一個唯一(Singleton)的服務對象。這個對象為所有的用戶端服務,并不會随用戶端終止而釋放。

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void Test();
}

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class MyServie : IMyService, IDisposable
{
    public MyServie()
    {
        Console.WriteLine("Constructor:{0}; {1}", DateTime.Now, this.GetHashCode());
    }

    [OperationBehavior]
    public void Test()
    {
        Console.WriteLine("Test:{0}; {1}", DateTime.Now, OperationContext.Current.SessionId);
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose:{0}", DateTime.Now);
    }
}

public class WcfTest
{
    public static void Test()
    {
        AppDomain.CreateDomain("Server").DoCallBack(delegate
        {
            ServiceHost host = new ServiceHost(typeof(MyServie), new Uri("http://localhost:8080/MyService"));
            host.AddServiceEndpoint(typeof(IMyService), new BasicHttpBinding(), "");
            host.Open();
        });

        //-----------------------

        for (int i = 0; i < 2; i++)
        {
            IMyService channel = ChannelFactory<IMyService>.CreateChannel(new BasicHttpBinding(), 
                new EndpointAddress("http://localhost:8080/MyService"));
                
            using (channel as IDisposable)
            {
                channel.Test();
                channel.Test();
            }
        }
    }
}      

Constructor:2007-4-17 17:31:01; 63238509

Test:2007-4-17 17:31:03;

還有另外一種方式來啟動 Single ServiceHost。

AppDomain.CreateDomain("Server").DoCallBack(delegate
{
    MyServie service = new MyServie();

    ServiceHost host = new ServiceHost(service, new Uri("http://localhost:8080/MyService"));
    host.AddServiceEndpoint(typeof(IMyService), new BasicHttpBinding(), "");
    host.Open();
});      

此方式最大的好處是允許我們使用非預設構造,除此之外,和上面的例子并沒有什麼差別。

需要特别注意的是,預設情況下,Single 會對服務方法進行并發控制。也就是說,多個用戶端需要排隊等待,直到排在前面的其他用戶端調用完成後才能繼續。看下面的例子。

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void Test();
}

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class MyServie : IMyService, IDisposable
{
    public MyServie()
    {
        Console.WriteLine("Constructor:{0}", this.GetHashCode());
    }

    [OperationBehavior]
    public void Test()
    {
        Console.WriteLine("Test:{0}", OperationContext.Current.SessionId);
        Thread.Sleep(2000);
        Console.WriteLine("Test End:{0}", OperationContext.Current.SessionId);
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose");
    }
}

public class WcfTest
{
    public static void Test()
    {
        AppDomain.CreateDomain("Server").DoCallBack(delegate
        {
            ServiceHost host = new ServiceHost(typeof(MyServie), new Uri("http://localhost:8080/MyService"));
            host.AddServiceEndpoint(typeof(IMyService), new WSHttpBinding(), "");
            host.Open();
        });

        //-----------------------

        for (int i = 0; i < 2; i++)
        {
            new Thread(delegate()
            {
                IMyService channel = ChannelFactory<IMyService>.CreateChannel(new WSHttpBinding(), 
                    new EndpointAddress("http://localhost:8080/MyService"));
                    
                using (channel as IDisposable)
                {
                    while (true)
                    {
                        channel.Test();
                    }
                }
            }).Start();
        }
    }
}      

Constructor:63238509

Test:urn:uuid:d613cfce-d454-40c9-9f4d-62b30a93ff27

Test End:urn:uuid:d613cfce-d454-40c9-9f4d-62b30a93ff27

Test:urn:uuid:313de31e-96b8-47c2-8cf3-7ae743236dfc

Test End:urn:uuid:313de31e-96b8-47c2-8cf3-7ae743236dfc

...

我們可以通過修改并發模式(ConcurrencyMode)來改變這種行為,但需要自己維護多線程安全。

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Multiple)]
public class MyServie : IMyService, IDisposable
{
    //...
}
      

Constructor:10261382

Test:urn:uuid:2bdac798-f774-40f2-b8f9-58de8d599d3c

Test:urn:uuid:9a328d0e-8df7-4885-94ed-e04ceedc5a09

Test End:urn:uuid:2bdac798-f774-40f2-b8f9-58de8d599d3c

Test End:urn:uuid:9a328d0e-8df7-4885-94ed-e04ceedc5a09