程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF後續之旅(12) 線程關聯性(Thread Affinity)對WCF並發訪問的影響

WCF後續之旅(12) 線程關聯性(Thread Affinity)對WCF並發訪問的影響

編輯:關於.NET

在本系列的上一篇文章中,我們重點討論了線程關聯性對service和callback的操作執行的影響:在service host的時候,可以設置當前線程的SynchronizationContext,那麼在默認情況下,service操作的執行將在該SynchronizationContext下執行(也就將service操作包裝成delegate傳入SynchronizationContext的Send或者Post方法);同理,對於Duplex同行方式來講,在client調用service之前,如果設置了當前線程的SynchronizationContext,callback操作也將自動在該SynchronizationContext下執行。

對於Windows Form Application來講,由於UI Control的操作執行只能在control被創建的線程中被操作,所以一這樣的方式實現了自己的SynchronizationContext(WindowsFormsSynchronizationContext):將所有的操作Marshal到UI線程中。正因為如此,當我們通過Windows Form Application進行WCF service的host的時候,將會對service的並發執行帶來非常大的影響。

詳細講,由於WindowsFormsSynchronizationContext的Post或者Send方法,會將目標方法的執行傳到UI主線程,所以可以說,所有的service操作都在同一個線程下執行,如果有多個client的請求同時抵達,他們並不能像我們希望的那樣並發的執行,而只能逐個以串行的方式執行。

一、通過實例證明線程關聯性對並發的影響

我們可以通過一個簡單的例子證明:在默認的情況下,當我們通過Windows Form Application進行service host的時候,service的操作都是在同一個線程中執行的。我們照例創建如下的四層結構的WCF service應用:

1、Contract:IService

namespace Artech.ThreadAffinity2.Contracts
{
  [ServiceContract]
  public interface IService
  {
    [OperationContract]
    void DoSomething();
  }
}

2、Service:Service

namespace Artech.ThreadAffinity2.Services
{
  public class Service:IService
  {
    public static ListBox DispalyPanel
    { get; set; }

    public static SynchronizationContext SynchronizationContext
    { get; set; }

    #region IService Members

    public void DoSomething()
    {
      Thread.Sleep(5000);
      int threadID = Thread.CurrentThread.ManagedThreadId;
      DateTime endTime = DateTime.Now;
      SynchronizationContext.Post(delegate
      {
        DispalyPanel.Items.Add(string.Format("Serice execution ended at {0}, Thread ID: {1}",
          endTime, threadID));
      }, null);
    }

    #endregion
  }
}

為了演示對並發操作的影響,在DoSomething()中,我將線程休眠10s以模擬一個相對長時間的操作執行;為了能夠直觀地顯示操作執行的線程和執行完成的時間,我將他們都打印在host該service的Windows Form的ListBox中,該ListBox通過static property的方式在host的時候指定。並將對ListBox的操作通過UI線程的SynchronizationContext(也是通過static property的方式在host的時候指定)的Post中執行(實際上,在默認的配置下,不需要如此,因為service操作的執行始終在Host service的UI線程下)。

3、Hosting

我們將service 的host放在一個Windows Form Application的某個一個Form的Load事件中。該Form僅僅具有一個ListBox:

namespace Artech.ThreadAffinity2.Hosting
{
  public partial class HostForm : Form
  {
    private ServiceHost _serviceHost;

    public HostForm()
    {
      InitializeComponent();
    }

    private void HostForm_Load(object sender, EventArgs e)
    {
      this.listBoxResult.Items.Add(string.Format("The ID of the Main Thread: {0}", Thread.CurrentThread.ManagedThreadId));
      this._serviceHost = new ServiceHost(typeof(Service));
      this._serviceHost.Opened += delegate
      {
        this.Text = "Service has been started up!";
      };
      Service.DispalyPanel = this.listBoxResult;
      Service.SynchronizationContext = SynchronizationContext.Current;
      this._serviceHost.Open();
    }

    private void HostForm_FormClosed(object sender, FormClosedEventArgs e)
    {
      this._serviceHost.Close();
    }
  }
}

在HostForm_Load,先在ListBox中顯示當前線程的ID,然後通過Service.DispalyPanel和Service.SynchronizationContext 為service的執行設置LisBox和SynchronizationContext ,最後將servicehost打開。下面是Configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Artech.ThreadAffinity2.Services.Service">
        <endpoint binding="basicHttpBinding" contract="Artech.ThreadAffinity2.Contracts.IService" />
        <host>
          <baseAddresses>
            <add baseAddress="http://127.0.0.1/service" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

4、Client

我們通過一個Console Application來模擬client端程序,先看看configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="http://127.0.0.1/service" binding="basicHttpBinding"
        contract="Artech.ThreadAffinity2.Contracts.IService" name="service" />
    </client>
  </system.serviceModel>
</configuration>

下面是service調用的代碼:

namespace Clients
{
  class Program
  {
    static void Main(string[] args)
    {
      using (ChannelFactory<IService> channelFactory = new ChannelFactory<IService>("service"))
      {
        IList<IService> channelList = new List<IService>();
        for (int i = 0; i < 10; i++)
        {
          channelList.Add(channelFactory.CreateChannel());
        }

        Array.ForEach<IService>(channelList.ToArray<IService>(),
          delegate(IService channel)
        {
          ThreadPool.QueueUserWorkItem(
          delegate
          {
            channel.DoSomething();
            Console.WriteLine("Service invocation ended at {0}", DateTime.Now);
          }, null);
        } );
        Console.Read();
      }
    }
  }
}

首先通過ChannelFactory<IService> 先後創建了10個Proxy對象,然後以異步的方式進行service的調用(為了簡單起見,直接通過ThreadPool實現異步調用),到service調用結束將當前時間輸出來。

我們來運行一下我們的程序,看看會出現怎樣的現象。先來看看service端的輸出結果:

通過上面的結果,從執行的時間來看service執行的並非並發,而是串行;從輸出的線程ID更能說明這一點:所有的操作的執行都在同一個線程中,並且service執行的線程就是host service的UI線程。這充分證明了service的執行具有與service host的線程關聯性。通過Server端的執行情況下,我們不難想象client端的執行情況。雖然我們是以異步的方式進行了10次service調用,但是由於service的執行並非並發執行,client的執行結果和同步下執行的情況並無二致:

二、解除線程的關聯性

在本系列的上一篇文章,我們介紹了service的線程關聯性通過ServiceBeahavior的UseSynchronizationContext控制。UseSynchronizationContext實際上代表的是是否使用預設的SynchronizationContext(實際上是DispatchRuntime的SynchronizationContext屬性中制定的)。我們對service的代碼進行如下簡單的修改,使service執行過程中不再使用預設的SynchronizationContext。

namespace Artech.ThreadAffinity2.Services
{
  [ServiceBehavior(UseSynchronizationContext = false)]
  public class Service:IService
  {

   ……
  }
}

再次運行我們的程序,看看現在具有怎樣的表現。首先看server端的輸出結果:

我們可以看出,service的執行並不在service host的主線程下,因為Thread ID不一樣,從時間上看,也可以看出它們是並發執行的。從Client的結果也可以證明這一點:

結論:當我們使用Windows Form Application進行service host的時候,首先應該考慮到在默認的情況下具有線程關聯特性。你需要評估的service的整個操作是否真的需要依賴於當前UI線程,如果不需要或者只有部分操作需要,將UseSynchronizationContext 設成false,將會提高service處理的並發量。對於依賴於當前UI線程的部分操作,可以通過SynchronizationContext實現將操作Marshal到UI線程中處理,對於這種操作,應該盡力那個縮短執行的時間。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved