[Architecture Design] 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
期許自己~
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。