C#委托、事件、多線程、Task淺析


 前言:最近看Xamarin中默認的項目代碼,發現使用了大量的Task類,因此回過頭來總結一下C#中委托、事件、多線程、Task在開發中的應用的應用,首先提一句看到一系列博客寫的特別好,地址是:http://www.cnblogs.com/sosowjb/archive/2012/08/11/2633953.html 。一共有6篇內容。我這篇主要內容就是總結自己用過的,盡可能寫出一些機理性的東西,更好理解。

委托delegate

        委托的字面意思就是請別人幫忙,它也確實是干這個事的。如果對C++比較了解,委托比較像指向類或者指向方法的指針。在C++中我們可以這樣:定義一個基類,在基類中定義方法的原型(無具體內容),在派生中具體寫方法。定義一個指向這個基類、派生類實例中方法的指針,通過指針直接訪問類中的方法。委托也基本上做了上面的事情,不過過程要簡單很多,你已經看不見指針了。

為什么要使用委托?

        先定義方法的原型,在外部寫原型中方法的具體內容,然后調用執行。想要做到這樣,就要用到委托(關於泛型委托的問題以后再說),在C#中所有的自定義事件都是用委托來實現的。先看看自定義事件要做個什么事:比如我有一個自定義控件,這個控件里面有一個自定義事件,當事件被觸發的時候要執行一個方法,這個方法是在類外部定義的,當我們使用這個控件的時候才會確定這個自定義事件所執行的方法,怎么樣明白了吧,這不就是委托么?這不就是C++里面指向類的函數的指針嗎?

        在C#委托是一個獨立的存在,理解上可以看做C++的基類,關於委托的使用看前言里提到的博客就可以了,這里我直接轉載。首先定義一個委托,可以有返回值也可以無返回值。

public delegate string MyDelegate(int ms);

        例子中有返回string,輸入為int類型的ms,這樣就定義了一個委托,這個委托的輸入輸出都定義了,但是...這個委托是干什么的要在外部定義,這就是委托存在的意義!就好像我新建一個委托是一個快遞員,我們知道他可以送快遞,但是送什么快遞送到哪里是由用戶決定的,這個快遞員就是委托,他不能決定送什么快遞,這就是委托(嗯,這個例子不錯!)

隨后定義委托的方法,對應上面的例子我們給快遞員描述一下快遞送到哪里,對應C++就是給基類指向方法的指針賦值。

static string DelegateMethod(int ms)
{
Console.WriteLine("把快遞送到火星");
Thread.Sleep(ms);
Console.WriteLine("已到達");
return "Hello world!";
}

 

在主線程調用方法:

static void Main1()
{
 MyDelegate dl = DelegateMethod;//給委托定義方法,在例子中,相當於把快遞怎么送告訴這個快遞員  
 IAsyncResult ar = dl.BeginInvoke(5000, null, null); //執行委托,執行方法
 while (!ar.IsCompleted)
 {
 Console.Write(".");
 Thread.Sleep(50);
 }
 string result = dl.EndInvoke(ar);//獲取有返回值的委托的返回值
 Console.WriteLine("result: {0}", result);
 } 

 

        可以看到,委托線程執行5秒鍾,主線程不停的循環判斷委托線程是否完成(用IsCompleted屬性判斷)。如果沒完成則斷續打點,如果完成則跳出循環。用EndInvoke方法獲取委托的返回值。如果方法未執行完,EndInvoke方法就會一直阻塞,直到被調用的方法執行完畢。BeignInvoke方法有3個輸入參數,更多用法可以查看前言提到的系列博客,也可以查看MSDN。  

自定義事件

    之前已經說了,在C#所有自定義事件都是委托,具體實現過程如下:

//自定義事件前,先要定義委托
public delegate void HitSuccess();
public event HitSuccess OnHitSuccess;

     這是一個小游戲中使用的一個委托,無輸入輸出,只執行方法,執行什么方法?不知道,這是要在類外面定義的。這樣自定義事件就完成了,觸發可以像方法一樣調用。

     OnHitSuccess();//觸發事件

多線程

    .Net的委托本質上就是指向函數的指針,只不過這種指針是經過封裝后類型安全的。委托和線程是兩個不同的概念,線程是動態的,委托就是一個或一組內存地址,是靜態的。線程執行時如果遇到了指向函數的指針就執行這個函數。
.Net為了方便編程,給委托賦予了兩種方式以供調用線程來執行,即同步和異步方式,它們分別通過Invoke和BeginInvoke來開啟。Invoke就是同步執行,由調用線程來執行,而BeginInvoke則開啟了一個后台線程來執行delegate所指向的函數,這個后台線程和調用線程之間屬於異步執行方式。實際上有了delegate這個概念,你在編程時就可以不用直接使用Thread類來開辟新的線程了,因為微軟替你實現了。

    使用BeginInvoke調用委托方法,其結果和調用一個新線程一樣。多線程編程在開發中經常用到,比如將后台計算和UI更新主線程分離,防止界面卡頓等,着重關注線程池ThreadPool,因為Task任務就是整理了它。

Task

    .NET 4包含新名稱空間System.Threading.Tasks,它 包含的類抽象出了線程功能。Task 在后台使用ThreadPool。 任務表示應完成的某個單元的工作。 這個單元的工作可以在單獨的線程中運行,也可以以同步方式啟動一個任務,這需要等待主調線程。 使用任務不僅可以獲得一個抽象層,還可以對底層線程進行很多控制。 

    在安排需要完成的工作時,任務提供了非常大的靈活性。 例如,可以定義連續的工作—— 在一個任務完成后該執行什么工作。 這可以區分任務成功與否。 另外,還可以在層次結構中安排任務。例如,父任務可以創建新的子任務。 這可以創建一種依賴關系,這樣,取消父任務,也會取消其子任務。
啟動一個Task

要啟動任務,可 以使用 TaskFactory類 或 Task類 的構造函數和 Start()方法。Task類的構造函數在創建任務上提供的靈活性較大。 
在啟動任務時,會創建Task類 的一個實例,利用Action或Action<object>委托不帶參數或帶一個object參數 ,可以指定應運行的代碼,這類似於Thread類 。下面定義了一個無參數的方法。 在實現代碼中,把任務的ID寫入控制台中: 

 

static void TaskMethod()
{
    Console.WriteLine("running in a task");
    Console.WriteLine("Task id: {0}",Task.CurrentId);
}

 

     在上面的代碼中,可 以看到啟動新任務的不同方式。第一種方式 使用實例化TaskFactory類 ,在其中把 TaskMedlod()方 法傳遞給StartNew()方法,就會立即啟動任務。 第二種方式使用 Task類的構造函數。 實例化 Task對象時,任務不會立即運行,而是指定 Created狀態。接着調用 Task類的Start()方法,來啟動任務。 使用Task類 時,除了調用 Start()方法,還可以調用RunSynchronously()方法。這樣,任務也會啟動,但在調用者的當前線程中它正在運行,調用者需要一直等待到該任務結束。 默認情況下,任務是異步運行的。

 

    //using task factory
    TaskFactory tf = new TaskFactory();
    Task t1 = tf.StartNew(TaskMethod);

    //using the task factory via a task
    Task t2 = Task.TaskFactory.StartNew(TaskMethod);

    //using Task constructor
    Task t3 = new Task(TaskMethod);
    t3.Start();

 

       使用Task類的構造函數和TaskFactory類的StartNew()方法時,都可以傳遞TaskCreationOptions枚舉中的值。設置LongRunning選項,可以通知任務調度器,該任務需要較長時間執行,這樣調度器更可能使用新線程。如果該任務應關聯到父任務上,而父任務取消了,則該任務也應取消,此時應設置 AuachToParent選項。PreferFairness的值表示,調度器應提取出已在等待的第一個任務。 如果一個任務在另一個任務內部創建,這就不是默認情況 。如果任務使用子任務創建了其他工作,子任務就優先於其他任務。 它們不會排在線程池隊列中的最后。 如果這些任務應以公平的方式與所有其他任務一起處理,就設置該選項為PreferFairness。 

 

Task t4 = new Task(TaskMethod, TaskCreationOptions.PreferFairness);
t4.Start();

Action,Func等委托

      泛型無返回值委托Action,有返回值Func,雖然使用Delegete委托可以達到同樣的效果,但是用Action等泛型委托寫法要更簡潔。除了Delegate委托我們還可以使用Action<T>和Func<T>委托。
      泛型Action<T>委托表示引用一個void返回類型的方法。Action<T>委托類存在不同的變體,可以傳遞至多16種不同的參數類型,沒有泛型參數的Action類可以調用沒有參數的方法。例如:Action<in T1>調用帶一個參數的方法,Action<in T1,in T2>調用帶兩個參數的方法等
      Func<T>的用法和Action<T>用法類似,但是Func<T>表示引用一個帶返回類型的方法,Func<T>也存在不同的變體,至多可以傳遞16個參數類型和1個返回類型,例如:Func<in T1,out Resout>表示帶一個參數的方法,Func<in T1,in T2,out Resout>表示調用帶兩個參數的方法。
    下面就直接給一個Action<T>和Func<T>的例子

 

using System;
namespace DelegateFuncAction
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<double, double,double> DoAddtion = calculate.addtion;
            double result = DoAddtion(20, 30);
            Console.WriteLine("Func帶返回參數委托做加法結果為:{0}",DoAddtion(10,20));
            calculate c=new calculate();
            Action<double, double> DoSubstraction = c.substraction;
            DoSubstraction(90, 20);
        }
    }
    class calculate
    {
        public static double addtion(double x, double y)
        {
            return x + y;
        }
        public void substraction(double x, double y)
        {
            Console.WriteLine("Action不帶返回參數委托做減法結果為:{0}",x-y);
        }
    }
}

 

后記Xamarin.Forms使用了大量的委托解決了大量的跨平台兼容性問題,如IOS和Android平台實現同樣功能實現的代碼不同,只需在Xamarin中建立委托,在各自平台中去實現委托內容即可。除了Delegate委托還大量使用了Action等泛型委托,個人覺得熟練掌握委托是學習Xamarin跨平台項目開發的一個關鍵。除此之外還同時大量使用了Task代替了ThreadPool。

 

 

·END· 雖然有種爛尾的感覺,但是就先寫到這了。如有錯誤,還請指正,我的博客原文地址


免責聲明!

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



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