天天看點

C#中的異步方法調用何謂同步,何謂異步 異步調用的本質 異步調用時參數的傳遞一個複雜一點的例子

翻譯自:http://www.c-sharpcorner.com/UploadFile/e70b61/asynchronous-methods-calls-in-C-Sharp/

(首次翻譯,翻譯得不對的地方請見諒!)

何謂同步,何謂異步

本文我将為大家介紹異步方法的調用。在進入正文之前,讓我們先來探讨一下何謂同步,何謂異步。假設在我們的main函數中調用了一個列印函數Print(),這個函數的執行過程是1秒,代碼片段如下:

function (calling function).

public void print()
 {

     Thread.Sleep(1000);

     Console.WriteLine("Hello World");
 }
           

調用開始後,再經過1秒鐘,控制台上将會列印出"Hello World",然後程式的控制權将會交還給調用它的函數(即main函數)。這就是所謂的異步調用。設想一下,若我們調用了100次Print()函數,将會消耗100*1秒的,而在這100秒時間内應用程式将不能執行其他任務。

而相比于同步調用"後續代碼需要等到同步函數執行完畢才能執行"的方式,異步調用一經調用便可立即傳回,即應用程式可以立即處理後面的事務,而不需要等待100秒的時間間隔。

異步調用的本質

異步調用的本質源于delegate機制。值得慶幸的是,delegate機制C#中很容易實作,我們隻需要聲明一個delegate類型變量,執行個體化後便可使用了。下面我們來看一個例子:

class Program

{
    public delegate void mymethod();

    static void Main(string[] args)
    {
        mymethod inv = new mymethod(print);

        inv.BeginInvoke(null, null);

        Console.WriteLine("I am Back");

        Console.Read();
    }

    public static  void print()
    {
        Thread.Sleep(1000);

        Console.WriteLine("Hello World");
    }
}
           

如上代碼片段所示,我先聲明了一個委托變量"mymethod",接着在main函數中建立了它的一個對象inv。注意到在main中,在main中構造inv的時候,我将print作為構造函數的參數,是以,當我調用BeginInvoke的後,将會在接下來的某個時刻執行Print函數。注意到,此處我用的是BeginInvoke()方法,而不是Invoke()。如果我使用Invoke(),那麼Print()函數的調用依舊是同步調用。

當我們以異步的方式調用了一個方法,該方法不會在調用方所處線程執行,否則這樣的調用就變成同步調用了。事實上,它(BeginInvoke方法)将會請求.Net線程池配置設定一條線程來執行Print函數。這裡需要了解一點,就是線程池中隻有25條線程可用,即是說,若線程池中的線程全部忙碌(正在執行先請求的事務),那麼Print函數将等到至少其中一條線程空閑時才會被執行。當其中一條線程處于非busy狀态時,控制器會立即擷取它的控制權,并在改線程中執行print方法。

至此,我們并不能确定通過代理機制執行的print函數是否執行完畢。要想知道什麼時候print方法執行結束,我們可以假設另外一種情境:當BeginInvoke一傳回時,在控制台上列印出"I am Not completed yet";當異步方法執行完畢時,控制台上列印出"Execution over "。要判斷Print()方法時候執行完畢,我們需要用到另外一個方法EndInvoke()。

如下代碼片段所示,執行完obj.EndInvoke()語句後,将等待Print()執行完畢後才會接着執行Console.WriteLine("Execution Over");

class Program
    {

        public delegate void mymethod();

        static void Main(string[] args)
        {
            mymethod obj = new mymethod(print);

            IAsyncResult tag = obj.BeginInvoke(null, null);

            Console.WriteLine("I am not completed yet");

            obj.EndInvoke(tag);

            Console.WriteLine("Execution Over");
        }

        public static void Print()
        {
            Thread.Sleep(10000);

            Console.WriteLine("Hello World");
        }
    }
           

異步調用時參數的傳遞

下面我們來了解下怎樣通過delegate和BeginInvoke()方法,傳遞一個參數給我們要調用的方法。如下代碼片段所示,我們聲明了一個帶有參數的delegate類型變量,接下來在調用委托的時候(obj.BeginInvoke)我們傳遞了一個值進去,這個值機會傳遞給Print函數。

public delegate void mymethod(string a);

   static void Main(string[] args)
   {
       mymethod obj = new mymethod (print);

       IAsyncResult tag= obj.BeginInvoke("Hello World",null, null);

       Console.WriteLine("I am not completed yet");

       obj.EndInvoke(tag);

       Console.WriteLine("Execution Over");
   }


   public static  void print(string param)
   {
       Thread.Sleep(1000);

       Console.WriteLine(param);
   }
           

一個複雜一點的例子

接下我們加些東西讓我們的example複雜一點。如果我們希望獲得Print函數的傳回值(如一個datatable),那麼我們就要用到AsyncCallback delegate--異步回調委托了。下面的例子中我們建立了一個異步委托執行個體,并作為BeginInvoke方法的參數。這樣一來,當異步操作(下例的Print函數)完成時,将會調用先前指定的回調函數Callback(IAsyncResult t)。在Callback函數裡我們便可以調用EndInvoke來獲得Print函數的傳回值了。

class Program
    {
        public delegate DataTable mymethod(string s);

        public static DataTable dt;

        public static mymethod inv;

        static void Main(string[] args)
        {
            inv = new mymethod(Print);

            inv.BeginInvoke("ahmar", new AsyncCallback(Callback), null);

            Console.ReadLine();
        }

        public static DataTable Print(string q)
        {
            Thread.Sleep(1000);

            Console.WriteLine(q);

            DataTable dt = new DataTable();

            dt.Columns.Add("Age");

            dt.Rows.Add(11);

            dt.Rows.Add(12);

            dt.Rows.Add(13);

            return dt;

        }

        public static void Callback(IAsyncResult t)
        {
            dt = inv.EndInvoke(t);

            foreach (DataRow row in dt.Rows)
            {
                Console.WriteLine(row["age"].ToString());
            }
        }
    }
           

上例輸出如下:

Output

Ahmar

11

12

13