# [你必須知道的異步編程]C# 5.0 新特性——Async和Await使異步編程更簡單
**本專題概要:**
**引言**
**同步代碼存在的問題**
**傳統的異步編程改善程序的響應**
**C# 5.0 提供的async和await使異步編程更簡單**
**async和await關鍵字剖析**
**小結**
## 一、引言
在之前的[C#基礎知識系列文章](http://www.cnblogs.com/zhili/archive/2013/01/08/CsharpIndex.html)中只介紹了從C#1.0到C#4.0中主要的特性,然而.NET 4.5 的推出,對于C#又有了新特性的增加——就是C#5.0中async和await兩個關鍵字,這兩個關鍵字簡化了異步編程,之所以簡化了,還是因為編譯器給我們做了更多的工作,下面就具體看看編譯器到底在背后幫我們做了哪些復雜的工作的。
## 二、同步代碼存在的問題
對于同步的代碼,大家肯定都不陌生,因為我們平常寫的代碼大部分都是同步的,然而同步代碼卻存在一個很嚴重的問題,例如我們向一個Web服務器發出一個請求時,如果我們發出請求的代碼是同步實現的話,這時候我們的應用程序就會處于等待狀態,直到收回一個響應信息為止,然而在這個等待的狀態,對于用戶不能操作任何的UI界面以及也沒有任何的消息,如果我們試圖去操作界面時,此時我們就會看到"應用程序為響應"的信息(在應用程序的窗口旁),相信大家在平常使用桌面軟件或者訪問web的時候,肯定都遇到過這樣類似的情況的,對于這個,大家肯定會覺得看上去非常不舒服。引起這個原因正是因為代碼的實現是同步實現的,所以在沒有得到一個響應消息之前,界面就成了一個"卡死"狀態了,所以這對于用戶來說肯定是不可接受的,因為如果我要從服務器上下載一個很大的文件時,此時我們甚至不能對窗體進行關閉的操作的。為了具體說明同步代碼存在的問題(造成界面開始),下面通過一個程序讓大家更形象地看下問題所在:
```
// 單擊事件
private void btnClick_Click(object sender, EventArgs e)
{
this.btnClick.Enabled = false;
long length = AccessWeb();
this.btnClick.Enabled = true;
// 這里可以做一些不依賴回復的操作
OtherWork();
this.richTextBox1.Text += String.Format("\n 回復的字節長度為: {0}.\r\n", length);
txbMainThreadID.Text = Thread.CurrentThread.ManagedThreadId.ToString();
}
private long AccessWeb()
{
MemoryStream content = new MemoryStream();
// 對MSDN發起一個Web請求
HttpWebRequest webRequest = WebRequest.Create("http://msdn.microsoft.com/zh-cn/") as HttpWebRequest;
if (webRequest != null)
{
// 返回回復結果
using (WebResponse response = webRequest.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
responseStream.CopyTo(content);
}
}
}
txbAsynMethodID.Text = Thread.CurrentThread.ManagedThreadId.ToString();
return content.Length;
}
```
運行程序后,當我們點擊窗體的 "點擊我"按鈕之后,在得到服務器響應之前,我們不能對窗體進行任何的操作,包括移動窗體,關閉窗體等,具體運行結果如下:

## 三、傳統的異步編程來改善程序的響應
上面部分我們已經看到同步方法所帶來的實際問題了,為了解決類似的問題,.NET Framework很早就提供了對異步編程的支持,下面就用.NET 1.0中提出的[異步編程模型(APM)](http://www.cnblogs.com/zhili/archive/2013/05/10/APM.html)來解決上面的問題,具體代碼如下(注釋的部分通過獲得GUI線程的同步上文對象,然后同步調用同步上下文對象的post方法把要調用的方法交給GUI線程去處理,因為控件本來就是由GUI線程創建的,然后由它自己執行訪問控件的操作就不存在跨線程的問題了,程序中使用的是調用**RichTextBox**控件的**Invoke**方式來異步回調訪問控件的方法,其實背后的原來和注釋部分是一樣的,調用**RichTextBox**控件的**Invoke**方法可以獲得創建RichTextBox控件的線程信息(也就是前一種方式的同步上下文),然后讓Invoke回調的方法在該線程上運行):
```
private void btnClick_Click(object sender, EventArgs e)
{
this.richTextBox1.Clear();
btnClick.Enabled = false;
AsyncMethodCaller caller = new AsyncMethodCaller(TestMethod);
IAsyncResult result = caller.BeginInvoke(GetResult, null);
//// 捕捉調用線程的同步上下文派生對象
//sc= SynchronizationContext.Current;
}
# region 使用APM實現異步編程
// 同步方法
private string TestMethod()
{
// 模擬做一些耗時的操作
// 實際項目中可能是讀取一個大文件或者從遠程服務器中獲取數據等。
for (int i = 0; i < 10; i++)
{
Thread.Sleep(200);
}
return "點擊我按鈕事件完成";
}
// 回調方法
private void GetResult(IAsyncResult result)
{
AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;
// 調用EndInvoke去等待異步調用完成并且獲得返回值
// 如果異步調用尚未完成,則 EndInvoke 會一直阻止調用線程,直到異步調用完成
string resultvalue = caller.EndInvoke(result);
//sc.Post(ShowState,resultvalue);
richTextBox1.Invoke(showStateCallback, resultvalue);
}
// 顯示結果到richTextBox
private void ShowState(object result)
{
richTextBox1.Text = result.ToString();
btnClick.Enabled = true;
}
// 顯示結果到richTextBox
//private void ShowState(string result)
//{
// richTextBox1.Text = result;
// btnClick.Enabled = true;
//}
#endregion
```
運行的結果為:

## 四、C# 5.0 提供的async和await使異步編程更簡單
上面部分演示了使用傳統的異步編程模型(APM)來解決同步代碼所存在的問題,然而在.NET 2.0,.NET 4.0和.NET 4.5中,微軟都有推出新的方式來解決同步代碼的問題,他們分別為基于事件的異步模式,基于任務的異步模式和提供async和await關鍵字來對異步編程支持。關于前兩種異步編程模式,在我前面的文章中都有介紹,大家可以查看相關文章進行詳細地了解,本部分就C# 5.0中的async和await這兩個關鍵字如何實現異步編程的問題來給大家介紹下。下面通過代碼來了解下如何使用async和await關鍵字來實現異步編程,并且大家也可以參看前面的博客來對比理解使用async和await是異步編程更簡單。
```
private async void btnClick_Click(object sender, EventArgs e)
{
long length = await AccessWebAsync();
// 這里可以做一些不依賴回復的操作
OtherWork();
this.richTextBox1.Text += String.Format("\n 回復的字節長度為: {0}.\r\n", length);
txbMainThreadID.Text = Thread.CurrentThread.ManagedThreadId.ToString();
}
// 使用C# 5.0中提供的async 和await關鍵字來定義異步方法
// 從代碼中可以看出C#5.0 中定義異步方法就像定義同步方法一樣簡單。
// 使用async 和await定義異步方法不會創建新線程,
// 它運行在現有線程上執行多個任務.
// 此時不知道大家有沒有一個疑問的?在現有線程上(即UI線程上)運行一個耗時的操作時,
// 為什么不會堵塞UI線程的呢?
// 這個問題的答案就是 當編譯器看到await關鍵字時,線程會
private async Task<long> AccessWebAsync()
{
MemoryStream content = new MemoryStream();
// 對MSDN發起一個Web請求
HttpWebRequest webRequest = WebRequest.Create("http://msdn.microsoft.com/zh-cn/") as HttpWebRequest;
if (webRequest != null)
{
// 返回回復結果
using (WebResponse response = await webRequest.GetResponseAsync())
{
using (Stream responseStream = response.GetResponseStream())
{
await responseStream.CopyToAsync(content);
}
}
}
txbAsynMethodID.Text = Thread.CurrentThread.ManagedThreadId.ToString() ;
return content.Length;
}
private void OtherWork()
{
this.richTextBox1.Text += "\r\n等待服務器回復中.................\n";
}
```
運行結果如下:

## 五、async和await關鍵字剖析
我們對比下上面使用async和await關鍵字來實現異步編程的代碼和在第二部分的同步代碼**,有沒有發現使用async和await關鍵字的異步實現和同步代碼的實現很像,只是異步實現中多了async和await關鍵字和調用的方法都多了async后綴而已。正是因為他們的實現很像,所以我在第四部分才命名為使用async和await使異步編程更簡單,就像我們在寫同步代碼一樣,并且代碼的coding思路也是和同步代碼一樣,這樣就避免考慮在APM中委托的回調等復雜的問題,以及在EAP中考慮各種事件的定義。**從代碼部分我們可以看出async和await的使用確實很簡單,我們就如在寫同步代碼一般,但是我很想知道編譯器到底給我們做了怎樣的處理的?并且從運行結果可以發現,運行異步方法的線程和GUI線程的ID是一樣的,也就是說**異步方法的運行在GUI線程上,所以就不用像APM中那樣考慮跨線程訪問的問題了(因為通過委托的BeginInvoke方法來進行回調方法時,回調方法是在線程池線程上執行的)。下面就用反射工具看看編譯器把我們的源碼編譯成什么樣子的:**
**對于按鈕點擊事件的代碼來說,編譯器生成的背后代碼卻是下面這樣的,完全和我們源碼中的兩個樣:**
```
// 編譯器為按鈕Click事件生成的代碼
private void btnClick_Click(object sender, EventArgs e)
{
<btnClick_Click>d__0 d__;
d__.<>4__this = this;
d__.sender = sender;
d__.e = e;
d__.<>t__builder = AsyncVoidMethodBuilder.Create();
d__.<>1__state = -1;
d__.<>t__builder.Start<<btnClick_Click>d__0>(ref d__);
}
```
看到上面的代碼,作為程序員的我想說——編譯器你怎么可以這樣呢?怎么可以任意篡改我的代碼呢?這樣不是侵犯我的版權了嗎?你要改最起碼應該告訴我一聲吧,如果我的源碼看到它在編譯器中的實現是上面那樣的,我相信我的源碼會說——難道我中了世間上最惡毒的面目全非腳嗎? 好吧,為了讓大家更好地理清編譯器背后到底做了什么事情,下面就順著上面的代碼摸瓜,我也來展示耍一套還我漂漂拳來幫助大家找到編譯器代碼和源碼的對應關系。我的分析思路為:
1、提出問題——我的click事件的源碼到哪里去了呢?
從編譯器代碼我們可以看到,前面的7句代碼都是對某個類進行賦值的操作,最真正起作用的就是最后Start方法的調用。這里又產生了幾個疑問——<btnClick_Click>d__0是什么類型? 該類型中的<>t__builder字段類型的Start方法到底是做什么用的? 有了這兩個疑問,我們就點擊<btnClick_Click>d__0(反射工具可以讓我們直接點擊查看)來看看它是什么類型
```
// <btnClick_Click>d__0類型的定義,從下面代碼可以看出它是一個結構體
// 該類型是編譯器生成的一個嵌入類型
// 看到該類型的實現有沒有讓你聯想到什么?
private struct <btnClick_Click>d__0 : IAsyncStateMachine
{
// Fields
public int <>1__state;
public Form1 <>4__this;
public AsyncVoidMethodBuilder <>t__builder;
private object <>t__stack;
private TaskAwaiter<long> <>u__$awaiter2;
public long <length>5__1;
public EventArgs e;
public object sender;
// Methods
private void MoveNext()
{
try
{
TaskAwaiter<long> CS$0$0001;
bool <>t__doFinallyBodies = true;
switch (this.<>1__state)
{
case -3:
goto Label_010E;
case 0:
break;
default:
// 獲取用于等待Task(任務)的等待者。你要知道某個任務是否完成,我們就需要一個等待者對象對該任務進行一個監控,所以微軟就定義了一個等待者對象的
// 從這里可以看出,其實async和await關鍵字背后的實現原理是基于任務的異步編程模式(TAP)
** // 這里代碼是在線程池線程上運行的**
CS$0$0001 = this.<>4__this.AccessWebAsync().GetAwaiter();
// 如果任務完成就調轉到Label_007A部分的代碼
if (CS$0$0001.IsCompleted)
{
goto Label_007A;
}
// 設置狀態為0為了退出回調方法。
this.<>1__state = 0;
this.<>u__$awaiter2 = CS$0$0001;
// 這個代碼是做什么用的呢?讓我們帶著問題看下面的分析
```
```
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<long>, Form1.<btnClick_Click>d__0>(ref CS$0$0001, ref this);
<>t__doFinallyBodies = false;
// 返回到調用線程,即GUI線程,這也是該方法不會堵塞GUI線程的原因,不管任務是否完成都返回到GUI線程
return;
}
// 當任務完成時,不會執行下面的代碼,會直接執行Label_007A中代碼
CS$0$0001 = this.<>u__$awaiter2;
this.<>u__$awaiter2 = new TaskAwaiter<long>();
// 為了使再次回調MoveNext代碼
this.<>1__state = -1;
Label_007A:
**// 下面代碼是在GUI線程上執行的**
CS$0$0001 = new TaskAwaiter<long>();
long CS$0$0003 = CS$0$0001.GetResult();
this.<length>5__1 = CS$0$0003;
// 我們源碼中的代碼這里的
**this.<>4__this.OtherWork();
this.<>4__this.richTextBox1.Text = this.<>4__this.richTextBox1.Text + string.Format("\n 回復的字節長度為: {0}.\r\n", this.<length>5__1);
this.<>4__this.txbMainThreadID.Text =** **Thread.CurrentThread.ManagedThreadId.ToString();**
}
catch (Exception <>t__ex)
{
this.<>1__state = -2;
this.<>t__builder.SetException(<>t__ex);
return;
}
Label_010E:
this.<>1__state = -2;
this.<>t__builder.SetResult();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine param0)
{
this.<>t__builder.SetStateMachine(param0);
}
}
```
如果你看過我的[迭代器專題](http://www.cnblogs.com/zhili/archive/2012/12/02/Interator.html)的話,相信你肯定可以聯想到該結構體就是一個迭代器的一個實現,其主要方法就是MoveNext方法。從上面的代碼的注釋應該可以幫助我們解決在第一步提到的第一個問題,即<btnClick_Click>d__0是什么類型,下面就分析下第二個問題,從<btnClick_Click>d__0結構體的代碼中可以發現<>t__builder的類型是**AsyncVoidMethodBuilder類型,下面就看看它的[Start](http://msdn.microsoft.com/zh-cn/library/hh472311.aspx)方法的解釋——運行關聯狀態機的生成器,即調用該方法就可以開始運行狀態機,運行狀態機指的就是執行MoveNext方法(MoveNext方法中有我們源碼中所有代碼,這樣就把編譯器生成的Click方法與我們的源碼關聯起來了)。從上面代碼注釋中可以發現,當該MoveNext被調用時會立即還回到GUI線程中,同時也有這樣的疑問——剛開始調用MoveNext方法時,任務肯定是還沒有被完成的,但是我們輸出我們源碼中的代碼,必須等待任務完成(因為任務完成才能調轉到Label_007A中的代碼),此時我們應該需要回調MoveNext方法來檢查任務是否完成,(就如迭代器中的,我們需要使用foreach語句一直調用MoveNext方法),然而我們在代碼卻沒有找到回調的任何代碼啊? 對于這個疑問,回調MoveNext方法肯定是存在的,只是首次看上面代碼的朋友還沒有找到類似的語句而已,上面代碼注釋中我提到了一個問題——這個代碼是做什么用的呢?讓我們帶著問題看下面的分析,其實注釋下面的代碼就是起到回調MoveNext方法的作用,[AsyncVoidMethodBuilder.AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>](http://msdn.microsoft.com/zh-cn/library/hh472325.aspx) 方法就是調度狀態機去執行MoveNext方法,從而也就解決了回調MoveNext的疑問了。**
相信大家從上面的解釋中可以找到源碼與編譯器代碼之間的對應關系了吧, 但是我在分析完上面的之后,又有一個疑問——當任務完成時,是如何退出MoveNext方法的呢?總不能讓其一直回調吧,從上面的代碼的注釋可以看出,當任務執行完成之后,會把<>1__state設置為0,當下次再回調MoveNext方法時就會直接退出方法,然而任務沒完成之前,同樣也會把<>1__state設置為0,但是Switch部分后面的代碼又把<>1__state設置為-1,這樣就保證了在任務沒完成之前,MoveNext方法可以被重復回調,當任務完成之后,<>1__state設置為-1的代碼將不會執行,而是調轉到Label_007A部分。
經過上面的分析之后,相信大家也可以耍出一套還我漂漂拳去分析異步方法**AccessWebAsync**(),其分析思路是和btnClick_Click的分析思路是一樣的.這里就不重復啰嗦了。
分析完之后,下面再分享下幾個關于async和await常問的問題
問題一:是不是寫了async關鍵字的方法就代表該方法是異步方法,不會堵塞線程呢?
答: 不是的,對于只標識async關鍵字的(指在方法內沒有出現await關鍵字)的方法,調用線程會把該方法當成同步方法一樣執行,所以然而會堵塞GUI線程,只有當async和await關鍵字同時出現,該方法才被轉換為異步方法處理。
問題二:“async”關鍵字會導致調用方法用線程池線程運行嗎?
答: 不會,被async關鍵字標識的方法不會影響方法是同步還是異步運行并完成,而是,它使方法可被分割成多個片段,其中一些片段可能異步運行,這樣這個方法可能異步完成。這些片段界限就出現在方法內部顯示使用”await”關鍵字的位置處。所以,如果在標記了”async”的方法中沒有顯示使用”await”,那么該方法只有一個片段,并且將以同步方式運行并完成。在await關鍵字出現的前面部分代碼和后面部分代碼都是同步執行的(即在調用線程上執行的,也就是GUI線程,所以不存在跨線程訪問控件的問題),await關鍵處的代碼片段是在線程池線程上執行。總結為——使用async和await關鍵字實現的異步方法,此時的異步方法被分成了多個代碼片段去執行的,而不是像之前的異步編程模型(APM)和EAP那樣,使用線程池線程去執行一整個方法。
關于更多async和await關鍵字的常問問題可以查看——[Async/Await FAQ](http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/async-await-faq.aspx)和中文翻譯——[(譯)關于async與await的FAQ](http://www.cnblogs.com/heyuquan/archive/2012/11/30/2795859.html)
## 六、小結
寫到這里本專題的內容就介紹到這里的,并且我也會把本專題的內容同步到之前的[C#基礎知識系列文章](http://www.cnblogs.com/zhili/archive/2013/01/08/CsharpIndex.html)索引,這樣我的C#特性系列也就完整了,并且該專題也是異步編程的最后一篇專題,在后面的專題將為大家實現一個類似迅雷的多任務多線程下載器,對于這個專題可能會用到并行編程的內容,所以接下面我為為大家分享下并行編程的內容。
根據 一路轉圈的雪人的建議,因為對于剛使用await的人,經常會問“幫來看一下怎么死鎖了,怎么辦啊,要死了,怎么解決?”,對于這樣的問題大家應該明白一點就是——**使用async標識的異步方法的運行在GUI線程上(對于這點大家一定要明白,在我文章中的剖析部分也詳細介紹了原因,閱讀文章的人應該重點了解),所以就不用像APM中那樣考慮跨線程訪問的問題了**。[](http://www.cnblogs.com/lwzz/)
本專題所有源碼下載:[ASyncAndAwaitTestProject.zip](http://files.cnblogs.com/zhili/ASyncAndAwaitTestProject.zip)
- C# 基礎知識系列
- C# 基礎知識系列 專題一:深入解析委托——C#中為什么要引入委托
- C# 基礎知識系列 專題二:委托的本質論
- C# 基礎知識系列 專題三:如何用委托包裝多個方法——委托鏈
- C# 基礎知識系列 專題四:事件揭秘
- C# 基礎知識系列 專題五:當點擊按鈕時觸發Click事件背后發生的事情
- C# 基礎知識系列 專題六:泛型基礎篇——為什么引入泛型
- C# 基礎知識系列 專題七: 泛型深入理解(一)
- C# 基礎知識系列 專題八: 深入理解泛型(二)
- C# 基礎知識系列 專題九: 深入理解泛型可變性
- C#基礎知識系列 專題十:全面解析可空類型
- C# 基礎知識系列 專題十一:匿名方法解析
- C#基礎知識系列 專題十二:迭代器
- C#基礎知識 專題十三:全面解析對象集合初始化器、匿名類型和隱式類型
- C# 基礎知識系列 專題十四:深入理解Lambda表達式
- C# 基礎知識系列 專題十五:全面解析擴展方法
- C# 基礎知識系列 專題十六:Linq介紹
- C#基礎知識系列 專題十七:深入理解動態類型
- 你必須知道的異步編程 C# 5.0 新特性——Async和Await使異步編程更簡單
- 全面解析C#中參數傳遞
- C#基礎知識系列 全面解析C#中靜態與非靜態
- C# 基礎知識系列 C#中易混淆的知識點
- C#進階系列
- C#進階系列 專題一:深入解析深拷貝和淺拷貝
- C#進階系列 專題二:你知道Dictionary查找速度為什么快嗎?
- C# 開發技巧系列
- C# 開發技巧系列 使用C#操作Word和Excel程序
- C# 開發技巧系列 使用C#操作幻燈片
- C# 開發技巧系列 如何動態設置屏幕分辨率
- C# 開發技巧系列 C#如何實現圖片查看器
- C# 開發技巧 如何防止程序多次運行
- C# 開發技巧 實現屬于自己的截圖工具
- C# 開發技巧 如何使不符合要求的元素等于離它最近的一個元素
- C# 線程處理系列
- C# 線程處理系列 專題一:線程基礎
- C# 線程處理系列 專題二:線程池中的工作者線程
- C# 線程處理系列 專題三:線程池中的I/O線程
- C# 線程處理系列 專題四:線程同步
- C# 線程處理系列 專題五:線程同步——事件構造
- C# 線程處理系列 專題六:線程同步——信號量和互斥體
- C# 多線程處理系列專題七——對多線程的補充
- C#網絡編程系列
- C# 網絡編程系列 專題一:網絡協議簡介
- C# 網絡編程系列 專題二:HTTP協議詳解
- C# 網絡編程系列 專題三:自定義Web服務器
- C# 網絡編程系列 專題四:自定義Web瀏覽器
- C# 網絡編程系列 專題五:TCP編程
- C# 網絡編程系列 專題六:UDP編程
- C# 網絡編程系列 專題七:UDP編程補充——UDP廣播程序的實現
- C# 網絡編程系列 專題八:P2P編程
- C# 網絡編程系列 專題九:實現類似QQ的即時通信程序
- C# 網絡編程系列 專題十:實現簡單的郵件收發器
- C# 網絡編程系列 專題十一:實現一個基于FTP協議的程序——文件上傳下載器
- C# 網絡編程系列 專題十二:實現一個簡單的FTP服務器
- C# 互操作性入門系列
- C# 互操作性入門系列(一):C#中互操作性介紹
- C# 互操作性入門系列(二):使用平臺調用調用Win32 函數
- C# 互操作性入門系列(三):平臺調用中的數據封送處理
- C# 互操作性入門系列(四):在C# 中調用COM組件
- CLR
- 談談: String 和StringBuilder區別和選擇
- 談談:程序集加載和反射
- 利用反射獲得委托和事件以及創建委托實例和添加事件處理程序
- 談談:.Net中的序列化和反序列化
- C#設計模式
- UML類圖符號 各種關系說明以及舉例
- C#設計模式(1)——單例模式
- C#設計模式(2)——簡單工廠模式
- C#設計模式(3)——工廠方法模式
- C#設計模式(4)——抽象工廠模式
- C#設計模式(5)——建造者模式(Builder Pattern)
- C#設計模式(6)——原型模式(Prototype Pattern)
- C#設計模式(7)——適配器模式(Adapter Pattern)
- C#設計模式(8)——橋接模式(Bridge Pattern)
- C#設計模式(9)——裝飾者模式(Decorator Pattern)
- C#設計模式(10)——組合模式(Composite Pattern)
- C#設計模式(11)——外觀模式(Facade Pattern)
- C#設計模式(12)——享元模式(Flyweight Pattern)
- C#設計模式(13)——代理模式(Proxy Pattern)
- C#設計模式(14)——模板方法模式(Template Method)
- C#設計模式(15)——命令模式(Command Pattern)
- C#設計模式(16)——迭代器模式(Iterator Pattern)
- C#設計模式(17)——觀察者模式(Observer Pattern)
- C#設計模式(18)——中介者模式(Mediator Pattern)
- C#設計模式(19)——狀態者模式(State Pattern)
- C#設計模式(20)——策略者模式(Stragety Pattern)
- C#設計模式(21)——責任鏈模式
- C#設計模式(22)——訪問者模式(Vistor Pattern)
- C#設計模式(23)——備忘錄模式(Memento Pattern)
- C#設計模式總結
- WPF快速入門系列
- WPF快速入門系列(1)——WPF布局概覽
- WPF快速入門系列(2)——深入解析依賴屬性
- WPF快速入門系列(3)——深入解析WPF事件機制
- WPF快速入門系列(4)——深入解析WPF綁定
- WPF快速入門系列(5)——深入解析WPF命令
- WPF快速入門系列(6)——WPF資源和樣式
- WPF快速入門系列(7)——深入解析WPF模板
- WPF快速入門系列(8)——MVVM快速入門
- WPF快速入門系列(9)——WPF任務管理工具實現
- ASP.NET 開發
- ASP.NET 開發必備知識點(1):如何讓Asp.net網站運行在自定義的Web服務器上
- ASP.NET 開發必備知識點(2):那些年追過的ASP.NET權限管理
- ASP.NET中實現回調
- 跟我一起學WCF
- 跟我一起學WCF(1)——MSMQ消息隊列
- 跟我一起學WCF(2)——利用.NET Remoting技術開發分布式應用
- 跟我一起學WCF(3)——利用Web Services開發分布式應用
- 跟我一起學WCF(3)——利用Web Services開發分布式應用
- 跟我一起學WCF(4)——第一個WCF程序
- 跟我一起學WCF(5)——深入解析服務契約 上篇
- 跟我一起學WCF(6)——深入解析服務契約 下篇
- 跟我一起學WCF(7)——WCF數據契約與序列化詳解
- 跟我一起學WCF(8)——WCF中Session、實例管理詳解
- 跟我一起學WCF(9)——WCF回調操作的實現
- 跟我一起學WCF(10)——WCF中事務處理
- 跟我一起學WCF(11)——WCF中隊列服務詳解
- 跟我一起學WCF(12)——WCF中Rest服務入門
- 跟我一起學WCF(13)——WCF系列總結
- .NET領域驅動設計實戰系列
- .NET領域驅動設計實戰系列 專題一:前期準備之EF CodeFirst
- .NET領域驅動設計實戰系列 專題二:結合領域驅動設計的面向服務架構來搭建網上書店
- .NET領域驅動設計實戰系列 專題三:前期準備之規約模式(Specification Pattern)
- .NET領域驅動設計實戰系列 專題四:前期準備之工作單元模式(Unit Of Work)
- .NET領域驅動設計實戰系列 專題五:網上書店規約模式、工作單元模式的引入以及購物車的實現
- .NET領域驅動設計實戰系列 專題六:DDD實踐案例:網上書店訂單功能的實現
- .NET領域驅動設計實戰系列 專題七:DDD實踐案例:引入事件驅動與中間件機制來實現后臺管理功能
- .NET領域驅動設計實戰系列 專題八:DDD案例:網上書店分布式消息隊列和分布式緩存的實現
- .NET領域驅動設計實戰系列 專題九:DDD案例:網上書店AOP和站點地圖的實現
- .NET領域驅動設計實戰系列 專題十:DDD擴展內容:全面剖析CQRS模式實現
- .NET領域驅動設計實戰系列 專題十一:.NET 領域驅動設計實戰系列總結