翻譯自: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