摘要:[Architecture] DI Thread Tips
套用IoC模式
在設計系統物件的時候,可以套用IoC模式來切割相依性。如下列範例程式碼,就是在Master、Slave兩個物件之間套用IoC的小小範例,在這個範例中NormalSlave會透過MessageNotified事件,來將執行訊息通知給Master。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Slave
ISlave slave = new NormalSlave();
// Master
Master master = new Master(slave);
// Execute
master.Execute();
// Wait
System.Threading.Thread.Sleep(5000);
Console.WriteLine("End");
}
}
public class Master
{
// Fields
private readonly ISlave _slave = null;
// Constructors
public Master(ISlave slave)
{
#region Contracts
if (slave==null) throw new ArgumentException();
#endregion
// Slave
_slave = slave;
_slave.MessageNotified += this.Slave_MessageNotified;
}
// Methods
public void Execute()
{
// Slave
_slave.Execute();
}
// Handlers
private void Slave_MessageNotified(string message)
{
#region Contracts
if (string.IsNullOrEmpty(message) == true) throw new ArgumentException();
#endregion
// Print
Console.WriteLine(string.Format("{0} {1}", DateTime.Now.ToString("HH:mm:ss"), message));
}
}
public interface ISlave
{
// Methods
void Execute();
// Events
event Action<string> MessageNotified;
}
public class NormalSlave : ISlave
{
// Methods
public void Execute()
{
this.OnMessageNotified("Work");
}
// Events
public event Action<string> MessageNotified;
private void OnMessageNotified(string message)
{
#region Contracts
if (string.IsNullOrEmpty(message) == true) throw new ArgumentException();
#endregion
var handler = this.MessageNotified;
if (handler != null)
{
handler(message);
}
}
}
}
注入包含Thread的實做
既然套用了IoC模式,就是為了後續可以注入不同的實做。這邊假設要注入一個實做是:ThreadSlave會透過MessageNotified事件,來將存活訊息定時通知給Master。而為了完成這個實做中的「定時通知」功能,在ThreadSlave中開啟一條Thread,用以定時執行通知。依照到目前為止的分析設計,可以建立出下列範例程式碼。
單從程式碼去分析下列的範例程式,會發現邏輯都是正確的。但是在執行之後馬上就會發現,因為在ThreadSlave裡面開啟了一條Thread,可是卻沒有程式碼去關閉Thread,所以這條Thread會持續的執行下去,而造成應用程式無法關閉。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
// Slave
ISlave slave = new ThreadSlave();
// Master
Master master = new Master(slave);
// Execute
master.Execute();
// Wait
System.Threading.Thread.Sleep(5000);
Console.WriteLine("End");
}
}
public class Master
{
// Fields
private readonly ISlave _slave = null;
// Constructors
public Master(ISlave slave)
{
#region Contracts
if (slave == null) throw new ArgumentException();
#endregion
// Slave
_slave = slave;
_slave.MessageNotified += this.Slave_MessageNotified;
}
// Methods
public void Execute()
{
// Slave
_slave.Execute();
}
// Handlers
private void Slave_MessageNotified(string message)
{
#region Contracts
if (string.IsNullOrEmpty(message) == true) throw new ArgumentException();
#endregion
// Print
Console.WriteLine(string.Format("{0} {1}", DateTime.Now.ToString("HH:mm:ss"), message));
}
}
public interface ISlave
{
// Methods
void Execute();
// Events
event Action<string> MessageNotified;
}
public class ThreadSlave : ISlave
{
// Fields
private readonly System.Threading.Thread _thread = null;
// Constructors
public ThreadSlave()
{
// Thread
_thread = new System.Threading.Thread(this.Operation);
_thread.Start();
}
// Methods
public void Execute()
{
this.OnMessageNotified("Work");
}
private void Operation()
{
while (true)
{
System.Threading.Thread.Sleep(1000);
this.OnMessageNotified("Alive");
}
}
// Events
public event Action<string> MessageNotified;
private void OnMessageNotified(string message)
{
#region Contracts
if (string.IsNullOrEmpty(message) == true) throw new ArgumentException();
#endregion
var handler = this.MessageNotified;
if (handler != null)
{
handler(message);
}
}
}
}
加入關閉Thread的Stop方法
上一個範例是因為開了Thread,卻沒有去關閉Thread,所以會造成應用程式無法關閉,那就加入Stop方法來關閉Thread讓程式正常關閉。依照這樣的分析設計,可以建立出下列範例程式碼。
這個範例程式碼,可以定時執行通知,並且正確的關閉了Thread,讓應用程式能夠正常關閉。但仔細思考加入Stop方法這件事,會發現這個Stop方法是用來管理ThreadSlave裡Thread的生命週期,對於NormalSlave來說顯得有點多餘,並且這個職責也不是Master的職責。也就是說Stop方法違反了物件導向設計的精神,是由下層界面的「特定實做」來變更上層物件。
另外再從整個系統架構來說,為了加入這個Stop方法,必須要從ThreadSlave、NormalSlave、ISlave、Master等等一路往上去做這個修改。這在系統小的時候,靠開發人員的辛勞,可以完成這樣的修改設計。但如果是一個龐大系統,系統裡的物件,已經像是端午節吃不完的肉粽那麼多,這個時候要來加入這個Stop方法的修改,除了勞民傷財之外,也很容易不小心改錯而造成系統執行的錯誤。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
// Slave
ISlave slave = new ThreadSlave();
// Master
Master master = new Master(slave);
// Execute
master.Execute();
// Wait
System.Threading.Thread.Sleep(5000);
Console.WriteLine("End");
// Stop
master.Stop();
}
}
public class Master
{
// Fields
private readonly ISlave _slave = null;
// Constructors
public Master(ISlave slave)
{
#region Contracts
if (slave == null) throw new ArgumentException();
#endregion
// Slave
_slave = slave;
_slave.MessageNotified += this.Slave_MessageNotified;
}
// Methods
public void Execute()
{
// Slave
_slave.Execute();
}
public void Stop()
{
// Slave
_slave.Stop();
}
// Handlers
private void Slave_MessageNotified(string message)
{
#region Contracts
if (string.IsNullOrEmpty(message) == true) throw new ArgumentException();
#endregion
// Print
Console.WriteLine(string.Format("{0} {1}", DateTime.Now.ToString("HH:mm:ss"), message));
}
}
public interface ISlave
{
// Methods
void Execute();
void Stop();
// Events
event Action<string> MessageNotified;
}
public class ThreadSlave : ISlave
{
// Fields
private readonly System.Threading.Thread _thread = null;
// Constructors
public ThreadSlave()
{
// Thread
_thread = new System.Threading.Thread(this.Operation);
_thread.Start();
}
// Methods
public void Execute()
{
this.OnMessageNotified("Work");
}
public void Stop()
{
_thread.Abort();
}
private void Operation()
{
while (true)
{
System.Threading.Thread.Sleep(1000);
this.OnMessageNotified("Alive");
}
}
// Events
public event Action<string> MessageNotified;
private void OnMessageNotified(string message)
{
#region Contracts
if (string.IsNullOrEmpty(message) == true) throw new ArgumentException();
#endregion
var handler = this.MessageNotified;
if (handler != null)
{
handler(message);
}
}
}
}
加入Thread.IsBackground = true的機制
在.NET中,為了簡化對於Thread這類資源的生命週期管理,為Thread類別加入了IsBackground屬性。.NET的CLR會在應用程式前景執行緒結束的時候,去檢查目前執行中的Thread,如果這個Thread的IsBackground設定為true,CLR就會主動去關閉這條Thread。藉由這樣自動關閉的功能,就能減少一些開發人員管理Thread生命週期的工作。
將這個機制套用到ThreadSlave裡,讓ThreadSlave所開啟的Thread,其生命週期交由CLR去管理。透過這樣的方式,在ThreadSlave中就不需要加入Stop方法來關閉Thread,進而不用再去修改NormalSlave、ISlave、Master等等物件,也就才能真正的享用到套用IoC的好處。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
// Slave
ISlave slave = new ThreadSlave();
// Master
Master master = new Master(slave);
// Execute
master.Execute();
// Wait
System.Threading.Thread.Sleep(5000);
Console.WriteLine("End");
}
}
public class Master
{
// Fields
private readonly ISlave _slave = null;
// Constructors
public Master(ISlave slave)
{
#region Contracts
if (slave == null) throw new ArgumentException();
#endregion
// Slave
_slave = slave;
_slave.MessageNotified += this.Slave_MessageNotified;
}
// Methods
public void Execute()
{
// Slave
_slave.Execute();
}
// Handlers
private void Slave_MessageNotified(string message)
{
#region Contracts
if (string.IsNullOrEmpty(message) == true) throw new ArgumentException();
#endregion
// Print
Console.WriteLine(string.Format("{0} {1}", DateTime.Now.ToString("HH:mm:ss"), message));
}
}
public interface ISlave
{
// Methods
void Execute();
// Events
event Action<string> MessageNotified;
}
public class ThreadSlave : ISlave
{
// Fields
private readonly System.Threading.Thread _thread = null;
// Constructors
public ThreadSlave()
{
// Thread
_thread = new System.Threading.Thread(this.Operation);
_thread.IsBackground = true;
_thread.Start();
}
// Methods
public void Execute()
{
this.OnMessageNotified("Work");
}
private void Operation()
{
while (true)
{
System.Threading.Thread.Sleep(1000);
this.OnMessageNotified("Alive");
}
}
// Events
public event Action<string> MessageNotified;
private void OnMessageNotified(string message)
{
#region Contracts
if (string.IsNullOrEmpty(message) == true) throw new ArgumentException();
#endregion
var handler = this.MessageNotified;
if (handler != null)
{
handler(message);
}
}
}
}
範例下載
後記
這篇文章乍看之下,會覺得最終只有介紹Thread.IsBackground這個屬性的功能。但主要是想透過這樣一個小小的範例演化,讓開發人員能夠體驗物件導向分析設計,過程中的一些考量:不要由特定實做來變更上層物件的職責、資源生命週期的管理(例如Thread)…等等。希望能透過這樣的文字說明,幫助到有需要的開發人員。:D
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。