一、CLR线程池基础
创建和销毁线程是一个昂贵的操作,所以CLR管理了一个线程池(thread pool),可以将线程池看成一个黑盒。
CLR初始化时,线程池中是没有线程的。线程的初始化与其他线程一样,但是在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。
二、 ThreadPool的简单使用
可以直接将一个运算放到线程池的队列中进行工作;
2.1 调用方式
向线程池的队列中添加一个”工作项“以及可选的状态数据,可以通过下面的两种方式来进行异步的操作:
public static bool QueueUserWorkItem(WaitCallback callBack);
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
2.2 使用示例
{
public static void Excute()
{
Console.WriteLine("Main Thread:开始队列进入一个异步线程;");
ThreadPool.QueueUserWorkItem(ComputeBoundOp, 5);
Console.WriteLine("Main Thread:做主线程的其他任务任务;");
//模拟其他任务
Thread.Sleep(10000);
Console.WriteLine("Main Thread End;");
}
private static void ComputeBoundOp(object state)
{
Console.WriteLine("In ComputeBoundOp: state={0}",state);
//模拟其他任务
Thread.Sleep(20000);
Console.WriteLine("ComputeBoundOp Thread End;");
}
}
运行结果:
2.3 取消线程的操作
简单的操作实例
public class CancellationDemo
{
/// <summary>
/// 构造一个CancellationTokenSource后,从它的Token属性中获得一个或多个CancellationToken实例,并传给你的操作,使操作可以取消
/// </summary>
public static void CancellationGo()
{
//构造一个CancellationTokenSource
CancellationTokenSource cts =new CancellationTokenSource();
//取消后的执行操作
cts.Token.Register(() => Console.WriteLine("canceled 1"));
cts.Token.Register(() => Console.WriteLine("canceled 2"));
//放入线程池开始工作
ThreadPool.QueueUserWorkItem(x => Count(cts.Token, 100));
Console.WriteLine("press to cancel the operation");
Console.ReadLine();
cts.Cancel();
}
/// <summary>
/// 计数
/// </summary>
/// <param name="token"></param>
/// <param name="countTo"></param>
private static void Count(CancellationToken token,Int32 countTo)
{
for (int i = 0; i < countTo; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("count is cancelled");
break;
}
Console.WriteLine(i);
Thread.Sleep(2000);
}
Console.WriteLine("count is done");
}
}
运行结果:
三、Task
很容易调用ThreadPool的QueueUserWorkItem方法发起一次异步的运算;但是,没有内建的机制让你知道操作在什么时候完成,也没有机制在操作完成时获得返回值;为了克服一些限制,引入了任务概念;
Task类是封装的一个任务类,内部使用的是ThreadPool类,提供了内建机制,让你知道什么时候异步完成以及如何获取异步执行的结果,并且还能取消异步执行的任务。
3.1 使用task和ThreadPool来完成相同的任务:
ThreadPool.QueueUserWorkItem(ComputeBoundOp, 5);
new Task(ComputeBoundOp,5).Start();
Task.Run(() => ComputeBoundOp(5));
3.2 Task的简单使用
public class TaskDemo
{
public void Excute()
{
long input = 10000000;
//创建一个task
Task<Int64> t1 = new Task<Int64>((x) => Sum((Int64) x),
input);
t1.Start();
//显示等待任务执行完毕
t1.Wait();
Console.WriteLine("task1 result is {0},主线程Id:{1}", t1.Result,Thread.CurrentThread.ManagedThreadId);
//演示可以取消的任务
CancellationTokenSource cst=new CancellationTokenSource();
//创建一个task
Task<Int64> t2 = new Task<Int64>(()=> Sum(cst.Token,input));
t2.Start();
Thread.Sleep(1000);
cst.Cancel();
Console.WriteLine("task2 result is {0}", t2.Result);
}
private static Int64 Sum(Int64 n)
{
Int64 sum = 0;
for (;n>0; n--)
{
checked
{
sum += n;
}
}
Console.WriteLine("计算线程Id:{0}",Thread.CurrentThread.ManagedThreadId);
return sum;
}
//创建可取消的任务
private static Int64 Sum(CancellationToken ct, Int64 n)
{
Int64 sum = 0;
for (; n > 0; n--)
{
// ct.ThrowIfCancellationRequested();
if (!ct.IsCancellationRequested)
{
checked
{
sum += n;
}
Thread.Sleep(10);
}
}
return sum;
}
}
运行结果:
我们可以发现,程序启动了一个新的线程来计算;
当我们添加了取消操作时,运算结果不同;
四、async和await
在.NET 4.5中引入的Async和Await两个新的关键字后,用户能以一种简洁直观的方式实现异步编程。甚至都不需要改变代码的逻辑结构,就能将原来的同步函数改造为异步函数。
在内部实现上,Async和Await这两个关键字由编译器转换为状态机,通过System.Threading.Tasks中的并行类实现代码的异步执行。
4.1 一个简单的例子
public class DownLoadUrlAsync
{
public void Excute()
{
Console.WriteLine("this is before Excute,threadId is {0}", Thread.CurrentThread.ManagedThreadId);
var t = GetUrl().ContinueWith(task =>
{
Console.WriteLine("this is ContinueWith Excute,threadId is {0},result is {1}",
Thread.CurrentThread.ManagedThreadId, task.Result);
});
Console.WriteLine("this is Excute,threadId is {0},t IsCompleted is {1}", Thread.CurrentThread.ManagedThreadId,t.IsCompleted);
}
private static async Task<int> GetUrl()
{
var httpClient = new HttpClient();
var url = "https://www.cnblogs.com/";
Console.WriteLine("this is before GetUrl,threadId is {0}", Thread.CurrentThread.ManagedThreadId);
var res = await httpClient.GetByteArrayAsync(url);
Console.WriteLine("this is GetUrl,threadId is {0}", Thread.CurrentThread.ManagedThreadId);
return res.Length;
}
}
运行结果:
通过结果可以发现,程序启用了新的线程来完成我们的任务;
五、小结
1、C#可以使用多种语法实现多线程 Thread
,ThreadPool
,Task
,async await
;
2、多线程可以同时完成多个任务;可以使程序的响应速度更快;可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给别的任务;