2012年4月23日 星期一

Design Pattern : Singleton in MultiThread ap


Design Pattern : Singleton Pattern

作者:JB

關於「Singleton, C# in Depth』已經描述了非常詳盡的細節。
我比較喜歡拿前輩的例子:取號機。
如果郵局有兩台取號機,如何保證同時兩台機器不會取到相同的號碼呢?
Singleton Pattern目的在於設立一個只能被建立一次instanceSingeton class,也就是說,如果我們在程式裡要嘗試建立第二個該Singeton classinstance時,是不被允許的。
本範例目的在於實做一支有四支執行緒的取號程式(亦即四個取號機同時取號)。
測試以下四種Singleton Pattern

1.      Non thread-safe
2.      Thread-safe using double-check locking
3.      Thread-safe, eager singleton
4.      Thread-safe, lazy singleton

一、 C# Windows Form應用程式為例,建立專案後,加入以下四個Singleton class
1.          Non thread-safe

public sealed class Counter01

{

  static Counter01 instance = null;

  private int CNT = 0;

  Counter01() //建構子

  {

    Console.WriteLine("建立Counter01物件!");

  }

  public static Counter01 GetInstance //建立或取得instance

  {

    get

    {

      if (instance == null)

      {

        instance = new Counter01();

      }

      return instance;

    }

  }

  public int GetNumber() //取得新號碼

  {

    CNT++;

    return CNT;

  }

}



備註1. 宣告關鍵字sealed class 表示此類別不可被繼承,詳見MSDN
備註2. 由建構子的輸出,可在runtime看出此類別實際上被建立了幾個instance
備註3. GetInstance這個事件,目的在於建立或取得靜態的instance
       Non thread-safe
指的就是這一段。

2.          Thread-safe using double-check locking
static Counter02 instance = null;
static readonly object padlock = new object(); //用來LOCK建立instance的程序。
private int CNT = 0;
Counter02()
{
 
Console.WriteLine("建立Counter02物件!");
}
public static Counter02 GetInstance
{
 
get
 
{
   
if(instance==null)
   
{
     
lock(padlock) //lock此區段程式碼,讓其它thread無法進入。
     
{
       
if(instance==null)
       
{
         
instance = new Counter02();
       
}
     
}
   
}
   
return instance;
 
}
}
public int GetNumber()
{
 
CNT++;
 
return CNT;
}

這邊跟第一種寫法的不同處,是使用lock(object) 來確定只有一條thread可以建立instance

3.           Thread-safe, eager singleton
因為第二種寫法過於繁雜,故有大師再改寫為第三種,亦即在類別裡面,直接宣告了instance = new Counter03,所以當我們使用Counter03時,instance會隨即建立, 所以稱為eager (急性子的) singleton
因為宣告為靜態的的變數,每支ap只會建立一次,所以它是thread-safe
public sealed class Counter03
{
  static Counter03 instance = new Counter03();
  private int CNT = 0;
  Counter03()
  {
    Console.WriteLine("
建立Counter03物件!");
  }
  public static Counter03 GetInstance
  {
    get
    {
      return instance;
    }
  }
  public int GetNumber()
  {
    CNT++;
    return CNT;
  }
}

備註1. 因為已經事先建立了instance,所以GetInstance並不用考慮建立instance的事情。

4.          Thread-safe, lazy singleton
第四種應該是終極版了,為了改善eager songleton的效能,大師們又想出了利用 inner class(也就是nested class)來建立instance。 當我們在呼叫GetInstance時,才會去建立這個inner class裡面的靜態instance
同樣的,因為是宣告為internal static readonly Counter04 instance,所以它是唯一,所以是thread-safe


public sealed class Counter04
{
 
private int CNT = 0;
 
Counter04()
 
{
   
Console.WriteLine("建立Counter04物件!");
 
}
 
public static Counter04 GetInstance
 
{
   
get
   
{
     
return InnerClass.instance;
   
}
 
}
 
public int GetNumber()
 
{
   
CNT++;
   
return CNT;
 
}

 
class InnerClass
 
{
   
static InnerClass()
   
{
   
}
   
internal static readonly Counter04 instance = new Counter04();
 
}
}
5.          其實在『C# in Depth』,有提到.NET4.0的新寫法: Lazy<T> type

二、 建立取號機(Thread)
建立了四種Singleton class後,我們接著來寫Multi-thread的部分,下面只列出部分程式碼囉。
目的是建立四台取號機,每台取號機各取五個號碼。 如果是thread-safe,四台取號機不會拿到相同的號碼,而且最大的號碼是20

1.          測試畫面

四個按鈕分別對應四種不同的Singleton方法。



2.          建立Global Thread集合、ThreadStart
private List<Thread> myThread;
ThreadStart Starter;

3.           在每個按鈕Click事件裡面,指定ThreadStart對應的事件 (指定取號方法的類別 Singleton class),然後再執行步驟4.

private void bt_Add1_Click(object sender, EventArgs e)
{
 
Starter = new ThreadStart(ThreadWork01);
 
Print();
}
/// <summary>
/// ThreadStart事件
/// </summary>
private void ThreadWork01()
{
 
for (int i = 1; i <= 5; i++)
 
{
   
int value = Counter01.GetInstance.GetNumber(); //指定取值方法的類別為Counter01
    String sOutput = String.Format("{0}  {1}-->
號碼:{2}",DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), Thread.CurrentThread.Name, value);
    
Console.WriteLine(sOutput);
    
Thread.Sleep(1000);
 
}
}

4.          建立四台取號機(四條Thread),然後開始取號(Thread.Start()
private void Print()
{
 
for (int index = 0; index < 4; index++)
 
{
    Thread thread;

   
thread = new Thread(Starter); //指定ThreadStart事件
   
thread.Name = "取號機-" + index.ToString();
   
myThread.Add(thread);
   
thread = null;
 
}
 
foreach (Thread thread in myThread)
 
{
   
thread.Start();
 
}
}
三、 執行結果
1.          Non thread-safe
建立Counter01物件!
建立Counter01物件!
2012/04/23 11:31:22  取號機-1-->號碼:1
2012/04/23 11:31:22  取號機-2-->號碼:2
2012/04/23 11:31:22  取號機-0-->號碼:1
2012/04/23 11:31:22  取號機-3-->號碼:3

2012/04/23 11:31:26  取號機-2-->號碼:18
2012/04/23 11:31:26  取號機-0-->號碼:19
可清楚的看到,Counter01被建立了兩次,所以出現取到兩次號碼 : 1
2.          Thread-safe using double-check locking
建立Counter02物件!
2012/04/23 11:35:44  取號機-2-->號碼:3
2012/04/23 11:35:44  取號機-1-->號碼:2
2012/04/23 11:35:44  取號機-0-->號碼:1
2012/04/23 11:35:44 
取號機-3-->號碼:4
2012/04/23 11:35:45  取號機-2-->號碼:6
2012/04/23 11:35:45  取號機-1-->號碼:5
2012/04/23 11:35:45  取號機-0-->號碼:7
2012/04/23 11:35:45  取號機-3-->號碼:8
2012/04/23 11:35:46  取號機-2-->號碼:10
2012/04/23 11:35:46  取號機-1-->號碼:9
2012/04/23 11:35:46 
取號機-0-->號碼:11
2012/04/23 11:35:46  取號機-3-->號碼:12
2012/04/23 11:35:47  取號機-1-->號碼:14
2012/04/23 11:35:47  取號機-2-->號碼:13
2012/04/23 11:35:47  取號機-3-->號碼:15
2012/04/23 11:35:47  取號機-0-->號碼:16
2012/04/23 11:35:48  取號機-2-->號碼:17
2012/04/23 11:35:48  取號機-1-->號碼:18
2012/04/23 11:35:48  取號機-0-->號碼:19
2012/04/23 11:35:48  取號機-3-->號碼:20

3.          Thread-safe, eager singleton
2.
4.          Thread-safe, lazy singleton
2.

2012年4月17日 星期二

寫好整合測試及壓力測試文件

今天終於寫好了專案的整合測試和壓力測試文件,
Visual Studio 2010的負載測試和效能精靈,終於可以派上用場了 :)

2012年4月16日 星期一

[C#] 建立第一支C# MSMQ程式 (First MSMQ program in C#)

How to create my first MSMQ program in C#

The FORCE studio

建立第一支C# MSMQ程式
作者:JB

We can read this article : 瞭解MSMQ,控制ASP進程 to get how a Message Queue Service works.

Before writing a MSMQ program , we must install the Message Queue Service in Windows.

We will use the library called
System.Messaging.MessageQueu to create and send our message in the C# project.

1         InstallTake Windows Vista for example
1.1      Install Message Queue service


1.2      The MSMQ manager is in 「控制台」「電腦管理」「訊息佇列」


1.3      We can check our MSMQ service as well.


2         Create my first MSMQ program in C# (Use a windows projectfor example)

2.1      The final output winform would be looked like this :

TexBox : The string message which will be send to the MSMQ.
Send msg to MQ】:Send the String in TextBox to MSMQ.
Start to get MQ】:Start the timer to get a new message from MSMQ.
Stop to get MQ】:Stop the timer to cancel getting messages from MSMQ.
TimerUsed to get the message from MSMQ every other time.

2.2       The Common method
MessageQueue.Formatter
MessageQueue.Label
MessageQueue.Path
MessageQueue.Peek
MessageQueue.Receive
MessageQueue.Send

Now we are going to write the codes!

2.3      Make a Queue Class
2.3.1             Property
public MessageQueue mq;
               public String sReceiveData;

2.3.2               Constructer
public Queue(String sType, String QueuePath)
{
 
try
 
{
    //
建立一個MSMQ
   
if (!MessageQueue.Exists(QueuePath))
   
{
     
MessageQueue.Create(QueuePath);
   
}
    //
MessageQueue物件指向 指定的MSMQ
   
mq = new MessageQueue(QueuePath);
   
mq.Formatter = new System.Messaging.XmlMessageFormatter();
   
//mq.ReceiveCompleted += MQ_ReceiveCompleted;
   
mq.Refresh();
   
sReceiveData = "";
 
}
 
catch (Exception ex)
 
{
   
throw ex;
 
}
}

2.3.3               Send message event
public void SendByLabel(String sBody, String sMsg)
{
 
try
 
{
   
mq.Send(sBody, sMsg);
 
}
 
catch (Exception ex)
 
{
   
throw ex;
 
}
}

2.3.4               Peek event
public bool Peek()
{
 
try
 
{
   
TimeSpan timeout = new TimeSpan(0, 0, 10); //設定timeout
   
if (mq.Peek(timeout) != null)
   
{
     
return true;
   
}
   
else
   
{
     
return false;
   
}
 
}
 
catch (MessageQueueException ex)
 
{
   
Console.WriteLine(ex.ErrorCode);
   
if (ex.ErrorCode == -2147467259) //已無訊息佇列
   
{
     
return false;
   
}
   
else
   
{
     
throw ex;
   
}
 
}
}

2.3.5               Receive event
public void Receive()
{
 
try
 
{
   
TimeSpan timeout = new TimeSpan(0, 0, 10); //設定timeout
   
Message msg = mq.Receive(timeout);
   
sReceiveData = msg.Label;
 
}
 
catch (Exception ex)
 
{
   
throw ex;
 
}
}

2.3.6               Done with class Queue.

2.4      Next step, we will create a global Queue item in our MainForm.cs:

Queue
MyQueue;

 And in the constructer of MainForm, we initial it …

MyQueue = new Queue("private", ".\\Private$\\MyQueue");

2.5      In the Click event of Send msg to MQ, we will send a message to MyQueue.
MyQueue.SendByLabel("MSMQ Msg Title", tb_send.Text);

Send a message to MSMQ will be looked like this…




2.6      Now how receive the message from MSMQ? We can only get ONE message each time.
So we can use the TIMER to repeat this step.
I set the Timer’s
Interval to 10000 (10 sec).
Surely you might change the value, but if the interval is too small, the program will PEEK the MSMQ too often that will get the performance pretty bad.
So in timer_Tick event, we write the codes for receiving a message…

private void timer_Tick(object sender, EventArgs e)
{
 
timer.Stop();
 
try
 
{
   
if (MyQueue.Peek() == true) //Peek訊息佇列
   
{
     
MyQueue.Receive(); //取出訊息佇列
     
String sMsg = MyQueue.sReceiveData;
     
if (sMsg.Length > 0)
     
{
       
lb_ReciveData.Items.Add(sMsg);
     
}
   
}
 
}
 
catch (Exception ex)
 
{
   
MessageBox.Show("timer_Tick失敗:" + ex.Message);
 
}
 
finally
 
{
   
timer.Start();
 
}
}

So we take a PEEK step before we RECEIVE it. The PEEK will return the first message in the MSMQ. The RECEIVE step will get it, that means delete the message after receiving it.

2.7      Since we finished the Timer event, the Start to get MQ/Stop to get MQ is used to start/stop the Timer.

2.8       Important!
Always set a TIMEOUT when doing PEEKRECEIVE.
   If not, your program will suspend while there is no message in the MSMQ.
If there is no message in the MSMQ, the method of PEEKRECEIVE will return a  
  
MessageQueueException. The error code is -2147467259.
   Remember to catch it!



3         Test screenshot