# [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);
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb31d99da.png)
从运行结果可以看出,调用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);
}
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb31e8e39.png)
从结果中可以得到,调用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);
}
}
}
}
}
```
运行结果为:
![](https://box.kancloud.cn/2016-01-23_56a2eb3204222.png)
如果把上面的 thread1.Interrupt();改为 thread1.Abort(); 运行结果为:
![](https://box.kancloud.cn/2016-01-23_56a2eb32147f4.png)
**二、线程池基础**
首先,创建和销毁线程是一个要耗费大量时间的过程,另外,太多的线程也会浪费内存资源,所以通过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());
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb3224e17.png)
从结果中可以看出,线程池中的可用的工作者线程少了一个,用去执行回调方法了。
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());
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb3242df9.png)
**四、使用委托实现异步**
通过调用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());
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb32583be.png)
**五、任务**
同样 任务的引入也是为了解决通过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());
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb326b304.png)
**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());
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb327b395.png)
**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());
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb328e308.png)
讲到这里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 领域驱动设计实战系列总结