C# Winform 基於Task的異步與延時執行


一、Task的機制

 
Task位於命名空間System.Threading.Tasks中,是.NET 4.0加入的新模塊,其實現機制大致類似於線程池ThreadPool,不過對於ThreadPool來說Task的優勢是很明顯的:
 
ThreadPool的實現機制:(一對多)
 
1、應用程序擁有一個用於存放委托的全局隊列;
2、使用ThreadPool.QueueUserWorkItem將新的委托加入到全局隊列;
3、線程池中的多個線程按照先進先出的方式取出委托並執行。
 
Task的實現機制:(多對多)
 
1、應用程序擁有一個用於存放Task(包裝的委托)的全局隊列(存放主程序創建的Task,標記為了TaskCreationOptions.PreferFairness的Task),以及線程池中每個工作線程對應的本地隊列(存放該工作線程創建的Task);
2、使用new Task()或Task.Factory.StartNew將新的Task加入到指定隊列;
3、線程池中的多個線程按照優先處理本地隊列,其次處理全局隊列的方式取出Task並執行;
4、如果工作線程A發現本地隊列為空(Task已處理完畢),那么A就會嘗試去全局隊列中獲取Task,如果全局隊列也為空,那么A就會到工作線程B的本地隊列中“竊取”一個Task來執行,這種策略很明顯的使得CPU更加充分的利用了並行執行。
 
 
 

二、Task的使用

 
創建Task並運行:
 
            //新建一個Task
            Task t1 = new Task(() => {
                Console.WriteLine("Task完成!");
            });
            //啟動Task
            t1.Start();

            Console.WriteLine("UI線程完成!");

上面是用new關鍵字創建,等同於如下使用Task.Factory(Task工廠)創建的方式:
 
            //新建一個Task(Task工廠創建,自動啟動)
            Task t1 = Task.Factory.StartNew(() => {
                Console.WriteLine("Task完成!");
            });

            Console.WriteLine("UI線程完成!");

這里為了簡便使用了Lambda表達式(=> 為Lambda運算符),上面兩部分代碼都等同於如下:
 
            void Test()
            {
                Console.WriteLine("Task完成!");
            }

            Action action = new Action(Test);

            //新建一個Task
            Task t1 = new Task(action);
            //啟動Task
            t1.Start();

            Console.WriteLine("UI線程完成!");

運行效果圖:
 
 
Task的執行方式有同步和異步兩種,上面的方式很明顯是異步執行,我們可以看到做為主線程的UI線程是先一步執行完的。
 
那么要怎么樣才能實現Task的同步執行呢?主要就這一個方法:Wait()!
 
代碼如下:
 
            //新建一個Task
            Task t1 = new Task(() => {
                Console.WriteLine("Task完成!");
            });
            //啟動Task
            t1.Start();

            Console.WriteLine("UI線程開始等待!");//①

            t1.Wait();

            Console.WriteLine("UI線程完成!");//②

運行效果圖:
 
 
主線程運行到t1.Wait()時,會讀取t1的狀態,當發現任務t1還未執行結束時,主線程便會阻塞在這個位置(只是阻塞在t1.Wait()位置,也就是說t1.Wait()之前的代碼①照舊執行),當讀取到t1的狀態為已經執行結束時,主線程才會再次恢復執行,從t1.Wait()之后的位置②繼續往下執行。
 
當然,當有多個任務都需要保持同步執行時,可以使用Task.WaitAll方法同時等待多個任務完成,代碼如下:
 
            //新建一個Task
            Task t1 = new Task(() => {
                Console.WriteLine("Task1完成!");
            });
            //新建一個Task
            Task t2 = new Task(() => {
                Console.WriteLine("Task2完成!");
            });
            //啟動Task
            t1.Start();
            t2.Start();

            Console.WriteLine("UI線程開始等待!");

            //等待t1,t2都完成
            Task.WaitAll(t1,t2);

            Console.WriteLine("UI線程完成!");

運行效果圖:
 
 
當然,對於Task的操作還有更多,這里對於我的需求無關緊要,所以不再列舉,詳情請參見MSDN的API文檔:
 
 
 

三、基於Task的異步與延時

 
我在這里進行了如下封裝:
 
        /// <summary>
        /// 開始一個異步任務
        /// </summary>
        /// <param name="taskAction">異步任務執行委托</param>
        /// <param name="taskEndAction">異步任務執行完畢后的委托(會跳轉回UI線程)</param>
        /// <param name="control">UI線程的控件</param>
        public void StartAsyncTask(Action taskAction, Action taskEndAction, Control control)
        {
            if (control == null)
            {
                return;
            }

            Task task = new Task(() => {
                try
                {
                    taskAction();

                    //返回UI線程
                    control.Invoke(new Action(() =>
                    {
                        taskEndAction();
                    }));
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message);
                }
            });

            task.Start();
        }

        /// <summary>
        /// 開始一個延時任務
        /// </summary>
        /// <param name="DelayTime">延時時長(秒)</param>
        /// <param name="taskEndAction">延時時間完畢之后執行的委托(會跳轉回UI線程)</param>
        /// <param name="control">UI線程的控件</param>
        public void StartDelayTask(int DelayTime, Action taskEndAction, Control control)
        {
            if (control == null)
            {
                return;
            }

            Task task = new Task(() => {
                try
                {
                    Thread.Sleep(DelayTime * 1000);

                    //返回UI線程
                    control.Invoke(new Action(() =>
                    {
                        taskEndAction();
                    }));
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message);
                }
            });

            task.Start();
        }

StartAsyncTask主要是執行一個異步操作,並在異步操作完成后執行指定的委托,這里因為Task的執行機制依然是多線程,由於winform的線程安全性使得非UI線程無法訪問UI線程中的UI控件,所以在Task操作結束后執行的委托有必要返回到UI線程中,也就是說StartAsyncTask主要的功能就是在taskAction中執行一系列的異步運算,運算結束之后在taskEndAction中進行一些可視化的表現,比如給某某UI控件賦值。
 
StartDelayTask幾乎等同於StartAsyncTask,只不過他更加的表現出來一種延時的特性,事實上在StartAsyncTask的taskAction中加入線程Sleep也就是StartDelayTask的效果了。
 
StartAsyncTask的使用,異步耗時操作:
 
            //顯示耗時等待界面(比如一串文字:正在加載,請稍等......)
            WaitPage.ShowWait();

            StartAsyncTask(
                () => {
                    //進行耗時操作......
                },
                () => {
                    //耗時操作完成,隱藏耗時等待界面
                    WaitPage.HideWait();
                },
                this);

StartDelayTask的使用,異步延時等待:
 
            Console.WriteLine("我軍將在三秒后發起反擊!");

            StartDelayTask(
                //等待的秒數
                3,
                () => {
                    //等待結束要做的事
                    Console.WriteLine("我軍開始反擊!");
                },
                this);

            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(String.Format("敵軍第{0}輪進攻!",i));
            }

運行效果圖:
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM