C#編程總結(jié)(六)詳解異步編程
1、什么是異步?
異步操作通常用于執(zhí)行完成時間可能較長的任務(wù),如打開大文件、連接遠(yuǎn)程計算機(jī)或查詢數(shù)據(jù)庫。異步操作在主應(yīng)用程序線程以外的線程中執(zhí)行。應(yīng)用程序調(diào)用方法異步執(zhí)行某個操作時,應(yīng)用程序可在異步方法執(zhí)行其任務(wù)時繼續(xù)執(zhí)行。
2、同步與異步的區(qū)別
同步(Synchronous):在執(zhí)行某個操作時,應(yīng)用程序必須等待該操作執(zhí)行完成后才能繼續(xù)執(zhí)行。
異步(Asynchronous):在執(zhí)行某個操作時,應(yīng)用程序可在異步操作執(zhí)行時繼續(xù)執(zhí)行。實(shí)質(zhì):異步操作,啟動了新的線程,主線程與方法線程并行執(zhí)行。
3、異步和多線程的區(qū)別
我們已經(jīng)知道,異步的實(shí)質(zhì)是開啟了新的線程。它與多線程的區(qū)別是什么呢?
簡單的說就是:異步線程是由線程池負(fù)責(zé)管理,而多線程,我們可以自己控制,當(dāng)然在多線程中我們也可以使用線程池。
就拿網(wǎng)絡(luò)扒蟲而言,如果使用異步模式去實(shí)現(xiàn),它使用線程池進(jìn)行管理。異步操作執(zhí)行時,會將操作丟給線程池中的某個工作線程來完成。當(dāng)開始I/O操作的時候,異步會將工作線程還給線程池,這意味著獲取網(wǎng)頁的工作不會再占用任何CPU資源了。直到異步完成,即獲取網(wǎng)頁完畢,異步才會通過回調(diào)的方式通知線程池??梢?,異步模式借助于線程池,極大地節(jié)約了CPU的資源。
注:DMA(Direct Memory Access)直接內(nèi)存存取,顧名思義DMA功能就是讓設(shè)備可以繞過處理器,直接由內(nèi)存來讀取資料。通過直接內(nèi)存訪問的數(shù)據(jù)交換幾乎可以不損耗CPU的資源。在硬件中,硬盤、網(wǎng)卡、聲卡、顯卡等都有直接內(nèi)存訪問功能。異步編程模型就是讓我們充分利用硬件的直接內(nèi)存訪問功能來釋放CPU的壓力。
兩者的應(yīng)用場景:
計算密集型工作,采用多線程。
IO密集型工作,采用異步機(jī)制。
4、異步應(yīng)用
.NET Framework 的許多方面都支持異步編程功能,這些方面包括:
1)文件 IO、流 IO、套接字 IO。
2)網(wǎng)絡(luò)。
3)遠(yuǎn)程處理信道(HTTP、TCP)和代理。
4)使用 ASP.NET 創(chuàng)建的 XML Web services。
5)ASP.NET Web 窗體。
6)使用 MessageQueue 類的消息隊列。
.NET Framework 為異步操作提供兩種設(shè)計模式:
1)使用 IAsyncResult 對象的異步操作。
2)使用事件的異步操作。
IAsyncResult 設(shè)計模式允許多種編程模型,但更加復(fù)雜不易學(xué)習(xí),可提供大多數(shù)應(yīng)用程序都不要求的靈活性??赡艿脑挘悗煸O(shè)計者應(yīng)使用事件驅(qū)動模型實(shí)現(xiàn)異步方法。在某些情況下,庫設(shè)計者還應(yīng)實(shí)現(xiàn)基于 IAsyncResult 的模型。
使用 IAsyncResult 設(shè)計模式的異步操作是通過名為 Begin操作名稱和End操作名稱的兩個方法來實(shí)現(xiàn)的,這兩個方法分別開始和結(jié)束異步操作操作名稱。例如,F(xiàn)ileStream 類提供 BeginRead 和 EndRead 方法來從文件異步讀取字節(jié)。這兩個方法實(shí)現(xiàn)了 Read 方法的異步版本。在調(diào)用 Begin操作名稱后,應(yīng)用程序可以繼續(xù)在調(diào)用線程上執(zhí)行指令,同時異步操作在另一個線程上執(zhí)行。每次調(diào)用 Begin操作名稱 時,應(yīng)用程序還應(yīng)調(diào)用 End操作名稱來獲取操作的結(jié)果。Begin操作名稱 方法開始異步操作操作名稱并返回一個實(shí)現(xiàn) IAsyncResult 接口的對象。 .NET Framework 允許您異步調(diào)用任何方法。定義與您需要調(diào)用的方法具有相同簽名的委托;公共語言運(yùn)行庫將自動為該委托定義具有適當(dāng)簽名的 BeginInvoke 和 EndInvoke 方法。
IAsyncResult 對象存儲有關(guān)異步操作的信息。下表提供了有關(guān)異步操作的信息。
|
名稱 |
說明 |
|
AsyncState |
獲取用戶定義的對象,它限定或包含關(guān)于異步操作的信息。 |
|
AsyncWaitHandle |
獲取用于等待異步操作完成的 WaitHandle。 |
|
CompletedSynchronously |
獲取一個值,該值指示異步操作是否同步完成。 |
|
IsCompleted |
獲取一個值,該值指示異步操作是否已完 |
5、應(yīng)用實(shí)例
案例1-讀取文件
通常讀取文件是一個比較耗時的工作,特別是讀取大文件的時候,常見的上傳和下載。但是我們又不想讓用戶一直等待,用戶同樣可以進(jìn)行其他操作,可以使得系統(tǒng)有良好的交互性。這里我們寫了同步調(diào)用和異步調(diào)用來進(jìn)行比較說明。
讀取文件類
using System;
using System.IO;
using System.Threading;
namespace AsynSample
{
class FileReader
{
/// <summary>
/// 緩存池
/// </summary>
private byte[] Buffer { get; set; }
/// <summary>
/// 緩存區(qū)大小
/// </summary>
public int BufferSize { get; set; }
public FileReader(int bufferSize)
{
this.BufferSize = bufferSize;
this.Buffer = new byte[BufferSize];
}
/// <summary>
/// 同步讀取文件
/// </summary>
/// <param name="path">文件路徑</param>
public void SynsReadFile(string path)
{
Console.WriteLine("同步讀取文件 begin");
using (FileStream fs = new FileStream(path, FileMode.Open))
{
fs.Read(Buffer, 0, BufferSize);
string output = System.Text.Encoding.UTF8.GetString(Buffer);
Console.WriteLine("讀取的文件信息:{0}",output);
}
Console.WriteLine("同步讀取文件 end");
}
/// <summary>
/// 異步讀取文件
/// </summary>
/// <param name="path"></param>
public void AsynReadFile(string path)
{
Console.WriteLine("異步讀取文件 begin");
//執(zhí)行Endread時報錯,fs已經(jīng)釋放,注意在異步中不能使用釋放需要的資源
//using (FileStream fs = new FileStream(path, FileMode.Open))
//{
// Buffer = new byte[BufferSize];
// fs.BeginRead(Buffer, 0, BufferSize, AsyncReadCallback, fs);
//}
if (File.Exists(path))
{
FileStream fs = new FileStream(path, FileMode.Open);
fs.BeginRead(Buffer, 0, BufferSize, AsyncReadCallback, fs);
}
else
{
Console.WriteLine("該文件不存在");
}
}
/// <summary>
///
/// </summary>
/// <param name="ar"></param>
void AsyncReadCallback(IAsyncResult ar)
{
FileStream stream = ar.AsyncState as FileStream;
if (stream != null)
{
Thread.Sleep(1000);
//讀取結(jié)束
stream.EndRead(ar);
stream.Close();
string output = System.Text.Encoding.UTF8.GetString(this.Buffer);
Console.WriteLine("讀取的文件信息:{0}", output);
}
}
}
}
測試用例
using System;
using System.Threading;
namespace AsynSample
{
class Program
{
static void Main(string[] args)
{
FileReader reader = new FileReader(1024);
//改為自己的文件路徑
string path = "C:\\Windows\\DAI.log";
Console.WriteLine("開始讀取文件了...");
//reader.SynsReadFile(path);
reader.AsynReadFile(path);
Console.WriteLine("我這里還有一大灘事呢.");
DoSomething();
Console.WriteLine("終于完事了,輸入任意鍵,歇著!");
Console.ReadKey();
}
/// <summary>
///
/// </summary>
static void DoSomething()
{
Thread.Sleep(1000);
for (int i = 0; i < 10000; i++)
{
if (i % 888 == 0)
{
Console.WriteLine("888的倍數(shù):{0}",i);
}
}
}
}
}
輸出結(jié)果:
同步輸出:

異步輸出:

結(jié)果分析:
如果是同步讀取,在讀取時,當(dāng)前線程讀取文件,只能等到讀取完畢,才能執(zhí)行以下的操作
而異步讀取,是創(chuàng)建了新的線程,讀取文件,而主線程,繼續(xù)執(zhí)行。我們可以開啟任務(wù)管理器來進(jìn)行監(jiān)視。
案例二--基于委托的異步操作
系統(tǒng)自帶一些類具有異步調(diào)用方式,如何使得自定義對象也具有異步功能呢?
我們可以借助委托來輕松實(shí)現(xiàn)異步。
說到BeginInvoke,EndInvoke就不得不停下來看一下委托的本質(zhì)。為了便于理解委托,我定義一個簡單的委托:
public delegate string MyFunc(int num, DateTime dt);
我們再來看一下這個委托在編譯后的程序集中是個什么樣的:
委托被編譯成一個新的類型,擁有BeginInvoke,EndInvoke,Invoke這三個方法。前二個方法的組合使用便可實(shí)現(xiàn)異步調(diào)用。第三個方法將以同步的方式調(diào)用。 其中BeginInvoke方法的最后二個參數(shù)用于回調(diào),其它參數(shù)則與委托的包裝方法的輸入?yún)?shù)是匹配的。 EndInvoke的返回值與委托的包裝方法的返回值匹配。
異步實(shí)現(xiàn)文件下載:
using System;
using System.Text;
namespace AsynSample
{
/// <summary>
/// 下載委托
/// </summary>
/// <param name="fileName"></param>
public delegate string AysnDownloadDelegate(string fileName);
/// <summary>
/// 通過委托實(shí)現(xiàn)異步調(diào)用
/// </summary>
class DownloadFile
{
/// <summary>
/// 同步下載
/// </summary>
/// <param name="fileName"></param>
public string Downloading(string fileName)
{
string filestr = string.Empty;
Console.WriteLine("下載事件開始執(zhí)行");
System.Threading.Thread.Sleep(3000);
Random rand = new Random();
StringBuilder builder =new StringBuilder();
int num;
for(int i=0;i<100;i++)
{
num = rand.Next(1000);
builder.Append(i);
}
filestr = builder.ToString();
Console.WriteLine("下載事件執(zhí)行結(jié)束");
return filestr;
}
/// <summary>
/// 異步下載
/// </summary>
public IAsyncResult BeginDownloading(string fileName)
{
string fileStr = string.Empty;
AysnDownloadDelegate downloadDelegate = new AysnDownloadDelegate(Downloading);
return downloadDelegate.BeginInvoke(fileName, Downloaded, downloadDelegate);
}
/// <summary>
/// 異步下載完成后事件
/// </summary>
/// <param name="result"></param>
private void Downloaded(IAsyncResult result)
{
AysnDownloadDelegate aysnDelegate = result.AsyncState as AysnDownloadDelegate;
if (aysnDelegate != null)
{
string fileStr = aysnDelegate.EndInvoke(result);
if (!string.IsNullOrEmpty(fileStr))
{
Console.WriteLine("下載文件:{0}", fileStr);
}
else
{
Console.WriteLine("下載數(shù)據(jù)為空!");
}
}
else
{
Console.WriteLine("下載數(shù)據(jù)為空!");
}
}
}
}
通過案例,我們發(fā)現(xiàn),使用委托能夠很輕易的實(shí)現(xiàn)異步。這樣,我們就可以自定義自己的異步操作了。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Unity3D實(shí)現(xiàn)控制攝像機(jī)移動
這篇文章主要為大家詳細(xì)介紹了Unity3D實(shí)現(xiàn)控制攝像機(jī)移動 ,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-02-02
C# TSC打印二維碼和條形碼的實(shí)現(xiàn)方法
下面小編就為大家分享一篇C# TSC打印二維碼和條形碼的實(shí)現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01
詳解如何利用C#實(shí)現(xiàn)設(shè)置系統(tǒng)時間
這篇文章主要為大家詳細(xì)介紹了如何利用C#實(shí)現(xiàn)設(shè)置系統(tǒng)時間功能,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12
C#向PPT文檔插入圖片以及導(dǎo)出圖片的實(shí)例
PowerPoint演示文稿是我們?nèi)粘9ぷ髦谐S玫霓k公軟件之一,本篇文章介紹了C#向PPT文檔插入圖片以及導(dǎo)出圖片的實(shí)例,非常具有實(shí)用價值,需要的朋友可以參考下。2016-12-12
C#實(shí)現(xiàn)簡單的計算器功能完整實(shí)例
這篇文章主要介紹了C#實(shí)現(xiàn)簡單的計算器功能,結(jié)合完整實(shí)例形式分析了C#實(shí)現(xiàn)常見的數(shù)學(xué)運(yùn)算、進(jìn)制轉(zhuǎn)換等相關(guān)操作技巧與界面布局方法,需要的朋友可以參考下2017-08-08

