[Architecture] DI Thread Tips

摘要:[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);
            }
        }
    }
}

 

 


 

 

範例下載

 

DIThreadTips.rar

 

 

 

 


 

 

 

後記

 

這篇文章乍看之下,會覺得最終只有介紹Thread.IsBackground這個屬性的功能。但主要是想透過這樣一個小小的範例演化,讓開發人員能夠體驗物件導向分析設計,過程中的一些考量:不要由特定實做來變更上層物件的職責、資源生命週期的管理(例如Thread)…等等。希望能透過這樣的文字說明,幫助到有需要的開發人員。:D

 

期許自己
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。