天天看點

C#多線程程式設計

一、使用線程的理由

1、可以使用線程将代碼同其他代碼隔離,提高應用程式的可靠性。

2、可以使用線程來簡化編碼。

3、可以使用線程來實作并發執行。

二、基本知識

1、程序與線程:程序作為作業系統執行程式的基本機關,擁有應用程式的資源,程序包含線程,程序的資源被線程共享,線程不擁有資源。

2、前台線程和背景線程:通過Thread類建立線程預設為前台線程。當所有前台線程關閉時,所有的背景線程也會被直接終止,不會抛出異常。

3、挂起(Suspend)和喚醒(Resume):由于線程的執行順序和程式的執行情況不可預知,是以使用挂起和喚醒容易發生死鎖的情況,在實際應用中應該盡量少用。

4、阻塞線程:Join,阻塞調用線程,直到該線程終止。

5、終止線程:Abort:抛出 ThreadAbortException 異常讓線程終止,終止後的線程不可喚醒。Interrupt:抛出 ThreadInterruptException 異常讓線程終止,通過捕獲異常可以繼續執行。

6、線程優先級:AboveNormal BelowNormal Highest Lowest Normal,預設為Normal。

三、線程的使用

線程函數通過委托傳遞,可以不帶參數,也可以帶參數(隻能有一個參數),可以用一個類或結構體封裝參數。

C#多線程程式設計

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(new ThreadStart(TestMethod));
            Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod));
            t1.IsBackground = true;
            t2.IsBackground = true;
            t1.Start();
            t2.Start("hello");
            Console.ReadKey();
        }

        public static void TestMethod()
        {
            Console.WriteLine("不帶參數的線程函數");
        }

        public static void TestMethod(object data)
        {
            string datastr = data as string;
            Console.WriteLine("帶參數的線程函數,參數為:{0}", datastr);
        }
    } 
}      
C#多線程程式設計

四、線程池

由于線程的建立和銷毀需要耗費一定的開銷,過多的使用線程會造成記憶體資源的浪費,出于對性能的考慮,于是引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務,然後委派給線程池的一個線程執行,線程執行完不會被立即銷毀,這樣既可以在背景執行任務,又可以減少線程建立和銷毀所帶來的開銷。

線程池線程預設為背景線程(IsBackground)。

C#多線程程式設計
namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            //将工作項加入到線程池隊列中,這裡可以傳遞一個線程參數
            ThreadPool.QueueUserWorkItem(TestMethod, "Hello");
            Console.ReadKey();
        }

        public static void TestMethod(object data)
        {
            string datastr = data as string;
            Console.WriteLine(datastr);
        }
    }
}      
C#多線程程式設計

五、Task類

使用ThreadPool的QueueUserWorkItem()方法發起一次異步的線程執行很簡單,但是該方法最大的問題是沒有一個内建的機制讓你知道操作什麼時候完成,有沒有一個内建的機制在操作完成後獲得一個傳回值。為此,可以使用System.Threading.Tasks中的Task類。

構造一個Task<TResult>對象,并為泛型TResult參數傳遞一個操作的傳回類型。

C#多線程程式設計
namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000);
            t.Start();
            t.Wait();
            Console.WriteLine(t.Result);
            Console.ReadKey();
        }

        private static Int32 Sum(Int32 n)
        {
            Int32 sum = 0;
            for (; n > 0; --n)
                checked{ sum += n;} //結果太大,抛出異常
            return sum;
        }
    }
}      
C#多線程程式設計

一個任務完成時,自動啟動一個新任務。

一個任務完成後,它可以啟動另一個任務,下面重寫了前面的代碼,不阻塞任何線程。

C#多線程程式設計
namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000);
            t.Start();
            //t.Wait();
            Task cwt = t.ContinueWith(task => Console.WriteLine("The result is {0}",t.Result));
            Console.ReadKey();
        }

        private static Int32 Sum(Int32 n)
        {
            Int32 sum = 0;
            for (; n > 0; --n)
                checked{ sum += n;} //結果溢出,抛出異常
            return sum;
        }
    }
}      
C#多線程程式設計

六、委托異步執行

委托的異步調用:BeginInvoke() 和 EndInvoke()

C#多線程程式設計
namespace Test
{
    public delegate string MyDelegate(object data);
    class Program
    {
        static void Main(string[] args)
        {
            MyDelegate mydelegate = new MyDelegate(TestMethod);
            IAsyncResult result = mydelegate.BeginInvoke("Thread Param", TestCallback, "Callback Param");

            //異步執行完成
            string resultstr = mydelegate.EndInvoke(result);
        }

        //線程函數
        public static string TestMethod(object data)
        {
            string datastr = data as string;
            return datastr;
        }

        //異步回調函數
        public static void TestCallback(IAsyncResult data)
        {
            Console.WriteLine(data.AsyncState);
        }
    }
}      
C#多線程程式設計

七、線程同步

  1)原子操作(Interlocked):所有方法都是執行一次原子讀取或一次寫入操作。

  2)lock()語句:避免鎖定public類型,否則執行個體将超出代碼控制的範圍,定義private對象來鎖定。

  3)Monitor實作線程同步

    通過Monitor.Enter() 和 Monitor.Exit()實作排它鎖的擷取和釋放,擷取之後獨占資源,不允許其他線程通路。

    還有一個TryEnter方法,請求不到資源時不會阻塞等待,可以設定逾時時間,擷取不到直接傳回false。

  4)ReaderWriterLock

    當對資源操作讀多寫少的時候,為了提高資源的使用率,讓讀操作鎖為共享鎖,多個線程可以并發讀取資源,而寫操作為獨占鎖,隻允許一個線程操作。

  5)事件(Event)類實作同步

    事件類有兩種狀态,終止狀态和非終止狀态,終止狀态時調用WaitOne可以請求成功,通過Set将時間狀态設定為終止狀态。

    1)AutoResetEvent(自動重置事件)

    2)ManualResetEvent(手動重置事件)

  6)信号量(Semaphore)

      信号量是由核心對象維護的int變量,為0時,線程阻塞,大于0時解除阻塞,當一個信号量上的等待線程解除阻塞後,信号量計數+1。

      線程通過WaitOne将信号量減1,通過Release将信号量加1,使用很簡單。

  7)互斥體(Mutex)

Qt

繼續閱讀