# [C# 線程處理系列]專題二:線程池中的工作者線程
**目錄:**
一、上節補充
二、CLR線程池基礎
三、通過線程池的工作者線程實現異步
四、使用委托實現異步
五、任務
**一、上節補充**
對于Thread類還有幾個常用方法需要說明的。
**1.1 Suspend和Resume方法**
這兩個方法在.net Framework 1.0的時候就支持的方法,他們分別可以掛起線程和恢復掛起的線程。但在.net Framework 2.0以后的版本中這兩個方法都過時了,MSDN的解釋是這樣:
警告:
Suspend and Resume methods to synchronize the activities of threads." data-guid="ff8e76fddd4f9d11989c7d25f5e342d1">不要使用 Suspend 和 Resume 方法來同步線程的活動。您無法知道掛起線程時它正在執行什么代碼。AppDomain might be blocked." data-guid="7c4587074f4e52d11eadc59e58a09a57">如果您在安全權限評估期間掛起持有鎖的線程,則 AppDomain中的其他線程可能被阻止。AppDomain that attempt to use that class are blocked." data-guid="f164549efd95a0a94479d4d1c0d1ceec">如果您在線程正在執行類構造函數時掛起它,則 AppDomain中嘗試使用該類的其他線程將被阻止。這樣很容易發生死鎖。
對于這個解釋可能有點抽象吧,讓我們來看看一段代碼可能會清晰點:
```
class Program
{
static void Main(string[] args)
{
// 創建一個線程來測試
Thread thread1 = new Thread(TestMethod);
thread1.Name = "Thread1";
thread1.Start();
Thread.Sleep(2000);
Console.WriteLine("Main Thread is running");
////int b = 0;
////int a = 3 / b;
////Console.WriteLine(a);
thread1.Resume();
Console.Read();
}
private static void TestMethod()
{
Console.WriteLine("Thread: {0} has been suspended!", Thread.CurrentThread.Name);
//將當前線程掛起
Thread.CurrentThread.Suspend();
Console.WriteLine("Thread: {0} has been resumed!", Thread.CurrentThread.Name);
}
}
```
在上面這段代碼中thread1線程是在主線程中恢復的,但當主線程發生異常時,這時候就thread1一直處于掛起狀態,此時thread1所使用的資源就不能釋放(除非強制終止進程),當另外線程需要使用這快資源的時候, 這時候就很可能發生死鎖現象。
上面一段代碼還存在一個隱患,請看下面一小段代碼:
```
class Program
{
static void Main(string[] args)
{
// 創建一個線程來測試
Thread thread1 = new Thread(TestMethod);
thread1.Name = "Thread1";
thread1.Start();
Console.WriteLine("Main Thread is running");
thread1.Resume();
Console.Read();
}
private static void TestMethod()
{
Console.WriteLine("Thread: {0} has been suspended!", Thread.CurrentThread.Name);
Thread.Sleep(1000);
//將當前線程掛起
Thread.CurrentThread.Suspend();
Console.WriteLine("Thread: {0} has been resumed!", Thread.CurrentThread.Name);
}
}
```
當主線程跑(運行)的太快,做完自己的事情去喚醒thread1時,此時thread1還沒有掛起而起喚醒thread1,此時就會出現異常了。并且上面使用的Suspend和Resume方法,編譯器已經出現警告了,提示這兩個方法已經過時, 所以在我們平時使用中應該盡量避免。
**1.2 Abort和 Interrupt方法**
Abort方法和Interrupt都是用來終止線程的,但是兩者還是有區別的。
1、他們拋出的異常不一樣,Abort 方法拋出的異常是ThreadAbortException, Interrupt拋出的異常為ThreadInterruptedException
2、調用interrupt方法的線程之后可以被喚醒,然而調用Abort方法的線程就直接被終止不能被喚醒的。
下面一段代碼是掩飾Abort方法的使用
```
using System;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Thread abortThread = new Thread(AbortMethod);
abortThread.Name = "Abort Thread";
abortThread.Start();
Thread.Sleep(1000);
try
{
abortThread.Abort();
}
catch
{
Console.WriteLine("{0} Exception happen in Main Thread", Thread.CurrentThread.Name);
Console.WriteLine("{0} Status is:{1} In Main Thread ", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
}
finally
{
Console.WriteLine("{0} Status is:{1} In Main Thread ", abortThread.Name, abortThread.ThreadState);
}
abortThread.Join();
Console.WriteLine("{0} Status is:{1} ", abortThread.Name, abortThread.ThreadState);
Console.Read();
}
private static void AbortMethod()
{
try
{
Thread.Sleep(5000);
}
catch(Exception e)
{
Console.WriteLine(e.GetType().Name);
Console.WriteLine("{0} Exception happen In Abort Thread", Thread.CurrentThread.Name);
Console.WriteLine("{0} Status is:{1} In Abort Thread ", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
}
finally
{
Console.WriteLine("{0} Status is:{1} In Abort Thread", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
}
}
}
```
運行結果:

從運行結果可以看出,調用Abort方法的線程引發的異常類型為ThreadAbortException, 以及異常只會在 調用Abort方法的線程中發生,而不會在主線程中拋出,并且調用Abort方法后線程的狀態不是立即改變為Aborted狀態,而是從AbortRequested->Aborted。
Interrupt方法:
```
using System;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{ Thread interruptThread = new Thread(AbortMethod);
interruptThread.Name = "Interrupt Thread";
interruptThread.Start();
interruptThread.Interrupt();
interruptThread.Join();
Console.WriteLine("{0} Status is:{1} ", interruptThread.Name, interruptThread.ThreadState);
Console.Read();
}
private static void AbortMethod()
{
try
{
Thread.Sleep(5000);
}
catch(Exception e)
{
Console.WriteLine(e.GetType().Name);
Console.WriteLine("{0} Exception happen In Interrupt Thread", Thread.CurrentThread.Name);
Console.WriteLine("{0} Status is:{1} In Interrupt Thread ", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
}
finally
{
Console.WriteLine("{0} Status is:{1} In Interrupt Thread", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
}
}
}
}
```
運行結果:

從結果中可以得到,調用Interrupt方法拋出的異常為:ThreadInterruptException, 以及當調用Interrupt方法后線程的狀態應該是中斷的, 但是從運行結果看此時的線程因為了Join,Sleep方法而喚醒了線程,為了進一步解釋調用Interrupt方法的線程可以被喚醒, 我們可以在線程執行的方法中運用循環,如果線程可以喚醒,則輸出結果中就一定會有循環的部分,然而調用Abort方法線程就直接終止,就不會有循環的部分,下面代碼相信大家看后肯定會更加理解兩個方法的區別的:
```
using System;
using System.Threading;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Thread thread1 = new Thread(TestMethod);
thread1.Start();
Thread.Sleep(100);
thread1.Interrupt();
Thread.Sleep(3000);
Console.WriteLine("after finnally block, the Thread1 status is:{0}", thread1.ThreadState);
Console.Read();
}
private static void TestMethod()
{
for (int i = 0; i < 4; i++)
{
try
{
Thread.Sleep(2000);
Console.WriteLine("Thread is Running");
}
catch (Exception e)
{
if (e != null)
{
Console.WriteLine("Exception {0} throw ", e.GetType().Name);
}
}
finally
{
Console.WriteLine("Current Thread status is:{0} ", Thread.CurrentThread.ThreadState);
}
}
}
}
}
```
運行結果為:

如果把上面的 thread1.Interrupt();改為 thread1.Abort(); 運行結果為:

**二、線程池基礎**
首先,創建和銷毀線程是一個要耗費大量時間的過程,另外,太多的線程也會浪費內存資源,所以通過Thread類來創建過多的線程反而有損于性能,為了改善這樣的問題 ,.net中就引入了線程池。
線程池形象的表示就是存放應用程序中使用的線程的一個集合(就是放線程的地方,這樣線程都放在一個地方就好管理了)。CLR初始化時,線程池中是沒有線程的,在內部, 線程池維護了一個操作請求隊列,當應用程序想執行一個異步操作時,就調用一個方法,就將一個任務放到線程池的隊列中,線程池中代碼從隊列中提取任務,將這個任務委派給一個線程池線程去執行,當線程池線程完成任務時,線程不會被銷毀,而是返回到線程池中,等待響應另一個請求。由于線程不被銷毀, 這樣就可以避免因為創建線程所產生的性能損失。
**注意:通過線程池創建的線程默認為后臺線程,優先級默認為Normal.**
**三、通過線程池的工作者線程實現異步**
**3.1 創建工作者線程的方法**
public static bool QueueUserWorkItem (WaitCallback callBack);
public static bool QueueUserWorkItem(WaitCallback callback, Object state);
這兩個方法向線程池的隊列添加一個工作項(work item)以及一個可選的狀態數據。然后,這兩個方法就會立即返回。
工作項其實就是由callback參數標識的一個方法,該方法將由線程池線程執行。同時寫的回調方法必須匹配System.Threading.WaitCallback委托類型,定義為:
public delegate void WaitCallback(Object state);
下面演示如何通過線程池線程來實現異步調用:
```
using System;
using System.Threading;
namespace ThreadPoolUse
{
class Program
{
static void Main(string[] args)
{
// 設置線程池中處于活動的線程的最大數目
// 設置線程池中工作者線程數量為1000,I/O線程數量為1000
ThreadPool.SetMaxThreads(1000, 1000);
Console.WriteLine("Main Thread: queue an asynchronous method");
PrintMessage("Main Thread Start");
// 把工作項添加到隊列中,此時線程池會用工作者線程去執行回調方法
ThreadPool.QueueUserWorkItem(asyncMethod);
Console.Read();
}
// 方法必須匹配WaitCallback委托
private static void asyncMethod(object state)
{
Thread.Sleep(1000);
PrintMessage("Asynchoronous Method");
Console.WriteLine("Asynchoronous thread has worked ");
}
// 打印線程池信息
private static void PrintMessage(String data)
{
int workthreadnumber;
int iothreadnumber;
// 獲得線程池中可用的線程,把獲得的可用工作者線程數量賦給workthreadnumber變量
// 獲得的可用I/O線程數量給iothreadnumber變量
ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workthreadnumber.ToString(),
iothreadnumber.ToString());
}
}
}
```
運行結果:

從結果中可以看出,線程池中的可用的工作者線程少了一個,用去執行回調方法了。
ThreadPool.QueueUserWorkItem(WaitCallback callback,Object state) 方法可以把object對象作為參數傳送到回調函數中,使用和ThreadPool.QueueUserWorkItem(WaitCallback callback)的使用和類似,這里就不列出了。
**3.2** **協作式取消**
.net Framework提供了**取消操作**的模式, 這個模式是協作式的。為了取消一個操作,首先必須創建一個**System.Threading.CancellationTokenSource**對象。
下面代碼演示了協作式取消的使用,主要實現當用戶在控制臺敲下回車鍵后就停止數數方法。
```
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(1000, 1000);
Console.WriteLine("Main thread run");
PrintMessage("Start");
Run();
Console.ReadKey();
}
private static void Run()
{
CancellationTokenSource cts = new CancellationTokenSource();
// 這里用Lambda表達式的方式和使用委托的效果一樣的,只是用了Lambda后可以少定義一個方法。
// 這在這里就是讓大家明白怎么lambda表達式如何由委托轉變的
////ThreadPool.QueueUserWorkItem(o => Count(cts.Token, 1000));
ThreadPool.QueueUserWorkItem(callback, cts.Token);
Console.WriteLine("Press Enter key to cancel the operation\n");
Console.ReadLine();
// 傳達取消請求
cts.Cancel();
}
private static void callback(object state)
{
Thread.Sleep(1000);
PrintMessage("Asynchoronous Method Start");
CancellationToken token =(CancellationToken)state;
Count(token, 1000);
}
// 執行的操作,當受到取消請求時停止數數
private static void Count(CancellationToken token,int countto)
{
for (int i = 0; i < countto; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Count is canceled");
break;
}
Console.WriteLine(i);
Thread.Sleep(300);
}
Console.WriteLine("Cout has done");
}
// 打印線程池信息
private static void PrintMessage(String data)
{
int workthreadnumber;
int iothreadnumber;
// 獲得線程池中可用的線程,把獲得的可用工作者線程數量賦給workthreadnumber變量
// 獲得的可用I/O線程數量給iothreadnumber變量
ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workthreadnumber.ToString(),
iothreadnumber.ToString());
}
}
}
```
運行結果:

**四、使用委托實現異步**
通過調用ThreadPool的QueueUserWorkItem方法來來啟動工作者線程非常方便,但委托WaitCallback指向的是帶有一個參數的無返回值的方法,如果我們實際操作中需要有返回值,或者需要帶有多個參數, 這時通過這樣的方式就難以實現, 為了解決這樣的問題,我們可以通過委托來建立工作這線程,
下面代碼演示了使用委托如何實現異步:
```
using System;
using System.Threading;
namespace Delegate
{
class Program
{
// 使用委托的實現的方式是使用了異步變成模型APM(Asynchronous Programming Model)
// 自定義委托
private delegate string MyTestdelegate();
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(1000, 1000);
PrintMessage("Main Thread Start");
//實例化委托
MyTestdelegate testdelegate = new MyTestdelegate(asyncMethod);
// 異步調用委托
IAsyncResult result = testdelegate.BeginInvoke(null, null);
// 獲取結果并打印出來
string returndata = testdelegate.EndInvoke(result);
Console.WriteLine(returndata);
Console.ReadLine();
}
private static string asyncMethod()
{
Thread.Sleep(1000);
PrintMessage("Asynchoronous Method");
return "Method has completed";
}
// 打印線程池信息
private static void PrintMessage(String data)
{
int workthreadnumber;
int iothreadnumber;
// 獲得線程池中可用的線程,把獲得的可用工作者線程數量賦給workthreadnumber變量
// 獲得的可用I/O線程數量給iothreadnumber變量
ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workthreadnumber.ToString(),
iothreadnumber.ToString());
}
}
}
```
運行結果:

**五、任務**
同樣 任務的引入也是為了解決通過ThreadPool.QueueUserWorkItem中限制的問題,
下面代碼演示通過任務來實現異步:
**5.1 使用任務來實現異步**
```
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskUse
{
class Program
{
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(1000, 1000);
PrintMessage("Main Thread Start");
// 調用構造函數創建Task對象,
Task<int> task = new Task<int>(n => asyncMethod((int)n), 10);
// 啟動任務
task.Start();
// 等待任務完成
task.Wait();
Console.WriteLine("The Method result is: "+task.Result);
Console.ReadLine();
}
private static int asyncMethod(int n)
{
Thread.Sleep(1000);
PrintMessage("Asynchoronous Method");
int sum = 0;
for (int i = 1; i < n; i++)
{
// 如果n太大,使用checked使下面代碼拋出異常
checked
{
sum += i;
}
}
return sum;
}
// 打印線程池信息
private static void PrintMessage(String data)
{
int workthreadnumber;
int iothreadnumber;
// 獲得線程池中可用的線程,把獲得的可用工作者線程數量賦給workthreadnumber變量
// 獲得的可用I/O線程數量給iothreadnumber變量
ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workthreadnumber.ToString(),
iothreadnumber.ToString());
}
}
}
```
運行結果:

**5.2 取消任務**
如果要取消任務, 同樣可以使用一個CancellationTokenSource對象來取消一個Task.
下面代碼演示了如何來取消一個任務:
```
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskUse
{
class Program
{
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(1000, 1000);
PrintMessage("Main Thread Start");
CancellationTokenSource cts = new CancellationTokenSource();
// 調用構造函數創建Task對象,將一個CancellationToken傳給Task構造器從而使Task和CancellationToken關聯起來
Task<int> task = new Task<int>(n => asyncMethod(cts.Token, (int)n), 10);
// 啟動任務
task.Start();
// 延遲取消任務
Thread.Sleep(3000);
// 取消任務
cts.Cancel();
Console.WriteLine("The Method result is: " + task.Result);
Console.ReadLine();
}
private static int asyncMethod(CancellationToken ct, int n)
{
Thread.Sleep(1000);
PrintMessage("Asynchoronous Method");
int sum = 0;
try
{
for (int i = 1; i < n; i++)
{
// 當CancellationTokenSource對象調用Cancel方法時,
// 就會引起OperationCanceledException異常
// 通過調用CancellationToken的ThrowIfCancellationRequested方法來定時檢查操作是否已經取消,
// 這個方法和CancellationToken的IsCancellationRequested屬性類似
ct.ThrowIfCancellationRequested();
Thread.Sleep(500);
// 如果n太大,使用checked使下面代碼拋出異常
checked
{
sum += i;
}
}
}
catch (Exception e)
{
Console.WriteLine("Exception is:" + e.GetType().Name);
Console.WriteLine("Operation is Canceled");
}
return sum;
}
// 打印線程池信息
private static void PrintMessage(String data)
{
int workthreadnumber;
int iothreadnumber;
// 獲得線程池中可用的線程,把獲得的可用工作者線程數量賦給workthreadnumber變量
// 獲得的可用I/O線程數量給iothreadnumber變量
ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workthreadnumber.ToString(),
iothreadnumber.ToString());
}
}
}
```
運行結果:

**5.3 任務工廠**
同樣可以通過任務工廠TaskFactory類型來實現異步操作。
```
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskFactory
{
class Program
{
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(1000, 1000);
Task.Factory.StartNew(() => PrintMessage("Main Thread"));
Console.Read();
}
// 打印線程池信息
private static void PrintMessage(String data)
{
int workthreadnumber;
int iothreadnumber;
// 獲得線程池中可用的線程,把獲得的可用工作者線程數量賦給workthreadnumber變量
// 獲得的可用I/O線程數量給iothreadnumber變量
ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);
Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workthreadnumber.ToString(),
iothreadnumber.ToString());
}
}
}
```
運行結果:

講到這里CLR的工作者線程大致講完了,希望也篇文章可以讓大家對線程又有進一步的理解。在后面的一篇線程系列將談談CLR線程池的I/O線程。
- 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 領域驅動設計實戰系列總結