在C#多线程之线程池篇中,我们将学习多线程访问共享资源的一些通用的技术,我们将学习到以下知识点:

  • 在线程池中调用委托
  • 在线程池中执行异步操作
  • 线程池和并行度
  • 实现取消选项
  • 使用等待句柄和超时
  • 使用计时器
  • 使用BackgroundWorker组件

  在前面的“C#多线程之基础篇”以及“C#多线程之线程同步篇”中,我们学习了如何创建线程以及如何使用多线程协同工作,在这一篇中,我们将学习另外一种场景,就是我们需要创建许多花费时间非常短的异步操作来完成某些工作。我们知道创建一个线程是非常昂贵的,因此,对于每个花费时间非常短的异步操作都创建一个线程是不合适的。

  我们可以使用线程池来解决以上问题,我们可以在线程池中分配一定数量的线程,每当我们需要一个线程时,我们只需要在线程池中取得一个线程即可,而不需要创建一个新的线程,当我们使用完一个线程时,我们仅仅需要把线程重新放入线程池中即可。

  我们可以使用System.Threading.ThreadPool类型来利用线程池。线程池由Common Language Runtime(CLR)进行管理,这意味着每一个CLR只能有一个线程池实例。ThreadPool类型有一个“QueueUserWorkItem”静态方法,这个静态方法接收一个委托,该委托代表一个用户自定义的异步操作。当这个方法被调用时,这个委托就进入内部队列,这个时候,如果线程池中没有线程,则会创建一个新的工作线程,然后将这个委托(第一个)放入队列中。

  如果先前的操作执行完毕后,我们又放置了一个新的操作到线程池,那么我们可能会重用上一次操作的那个工作线程。如果我们放置新的操作的时候,线程池中的线程数已达到上限,那么新的操作会在队列中等待,直到线程池中有可用工作线程为止。

  需要注意的是,我们尽量在线程池中放置一些需要花费较少时间既能完成的操作,而不要放置需要花费大量时间才能完成的操作,同时不要阻塞工作线程。如果不是这样,工作线程会变得非常繁忙,以至于不能响应用户操作,同时也会导致性能问题以及难以调试的错误。

  另外,线程池中的工作线程都是后台线程,这意味着当所有的前台线程执行完毕后,后台线程会被停止执行。

  在这一篇中,我们将学习如何使用线程池执行异步操作、如何取消一个操作以及如何防止长时间运行一个线程。

一、在线程池中调用委托

  在这一小节中,我们将学习如何在线程池中异步执行一个委托。为了演示如何在线程池中调用一个委托,执行以下操作步骤:

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,编写代码如下所示:

 using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread; namespace Recipe01
{
class Program
{
private delegate string RunOnThreadPool(out int threadId); private static string Test(out int threadId)
{
WriteLine("Starting...");
WriteLine($"Is thread pool thread: {CurrentThread.IsThreadPoolThread}");
Sleep(TimeSpan.FromSeconds());
threadId = CurrentThread.ManagedThreadId;
return $"Thread pool worker thread id was : {threadId}";
} private static void Callback(IAsyncResult ar)
{
WriteLine("Starting a callback...");
WriteLine($"State passed to a callback: {ar.AsyncState}");
WriteLine($"Is thread pool thread: {CurrentThread.IsThreadPoolThread}");
WriteLine($"Thread pool worker thread id: {CurrentThread.ManagedThreadId}");
} static void Main(string[] args)
{
int threadId = ;
var t = new Thread(() => Test(out threadId));
t.Start();
t.Join();
WriteLine($"Thread id: {threadId}"); RunOnThreadPool poolDelegate = Test;
IAsyncResult r = poolDelegate.BeginInvoke(out threadId, Callback, "a delegate asynchronous call");
r.AsyncWaitHandle.WaitOne();
string result = poolDelegate.EndInvoke(out threadId, r);
WriteLine($"Thread pool worker thread id: {threadId}");
WriteLine(result); Sleep(TimeSpan.FromSeconds());
}
}
}

3、运行该控制台应用程序,运行效果如下图所示:

  在第32行代码处,我们使用老办法创建了一个线程,然后启动它,并等待它执行完毕。因为thread的构造方法只接收不带返回值的委托方法,因此,我们给它传递一个lambda表达式,在该表达式中我们调用了“Test”方法。在“Test”方法中,我们使用“Thread.CurrentThread.IsThreadPoolThread”属性值来判断线程是不是来自线程池。我们还使用“CurrentThread.ManagedThreadId”属性值打印出运行当前代码的线程ID。

  在第10行代码处,我们定义了一个委托,该委托表示的方法的返回值为字符串类型,并且接收一个整型类型的输出参数。

  在第37行代码处,我们将Test方法赋值给poolDelegate委托,并在第38行代码处,使用委托的“BeginInvoke”方法运行该委托指向的方法(Test)。“BeginInvoke”接收一个回调方法,该方法将在异步操作完成之后被调用。“BeginInvoke”的第三个参数是传递给回调方法的一个用户自定义的状态。通常使用这个状态来分辨一个异步调用。我们使用“IAsyncResult”接口来保存“BeginInvoke”方法的返回值。

  “BeginInvoke”方法立即返回,这允许我们可以在线程池中的工作线程执行的同时,继续执行调用“BeginInvoke”方法的线程中的下一条代码。

  在第40行代码处,我们可以使用“BeginInvoke”方法的返回值以及对“EndInvoke”方法的调用来获得异步操作的结果。

  注意,第39行代码不是必须的,如果我们注释掉这一行代码,程序仍然运行成功,这是因为“EndInvoke”方法会一直等待异步操作完成。调用“EndInvoke”方法的另一个好处是在工作线程中任何未处理的异常都会抛给调用线程。

  如果我们注释掉第44行代码,回调方法“Callback”将不会被执行,这是因为主线程已经结束,所有的后台线程都会被停止。

二、在线程池中执行异步操作

  在这一小节中,我们将学习如何在线程池中执行异步操作,具体步骤如下:

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,编写代码如下所示:

 using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread; namespace Recipe02
{
class Program
{
private static void AsyncOperation(object state)
{
WriteLine($"Operation state: {state ?? "(null)"}");
WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}");
Sleep(TimeSpan.FromSeconds());
} static void Main(string[] args)
{
const int x = ;
const int y = ;
const string lambdaState = "lambda state 2"; ThreadPool.QueueUserWorkItem(AsyncOperation);
Sleep(TimeSpan.FromSeconds()); ThreadPool.QueueUserWorkItem(AsyncOperation, "async state");
Sleep(TimeSpan.FromSeconds()); ThreadPool.QueueUserWorkItem(state =>
{
WriteLine($"Operation state: {state}");
WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}");
Sleep(TimeSpan.FromSeconds());
}, "lambda state"); ThreadPool.QueueUserWorkItem(state =>
{
WriteLine($"Operation state: {x + y}, {lambdaState}");
WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}");
Sleep(TimeSpan.FromSeconds());
}, "lambda state"); Sleep(TimeSpan.FromSeconds());
}
}
}

3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

  在第10~15行代码处,我们定义了一个带有object类型参数的“AsyncOperation”方法,然后在第23行代码处,我们使用ThreadPool的“QueueUserWorkItem”静态方法在线程池中执行“AsyncOperation”方法。

  在第26行代码处,我们又一次使用了“QueueUserWorkItem”静态方法在线程池中执行“AsyncOperation”方法,只不过这次我们给“AsyncOperation”方法传递了state参数。

  在第24行和第27行代码处,我们让主线程阻塞2秒钟,以重用线程池中的工作线程。如果我们注释掉这两行代码,那么工作线程的线程ID大部分情况先将会不一样。

  在第29~41行代码中,我们演示了如何使用lambda表达式来进行线程池中的异步操作,请自行分析结果。

C#多线程之线程池篇1的更多相关文章

  1. C#多线程之线程池篇3

    在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识.在这一篇中,我们主要学习如何使用等待句柄和超时.使用计时器和使用BackgroundWorker组件的相关 ...

  2. C#多线程之线程池篇2

    在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...

  3. JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor

    JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...

  4. Qt中的多线程与线程池浅析+实例

    1. Qt中的多线程与线程池 今天学习了Qt中的多线程和线程池,特写这篇博客来记录一下 2. 多线程 2.1 线程类 QThread Qt 中提供了一个线程类,通过这个类就可以创建子线程了,Qt 中一 ...

  5. Java 多线程:线程池

    Java 多线程:线程池 作者:Grey 原文地址: 博客园:Java 多线程:线程池 CSDN:Java 多线程:线程池 工作原理 线程池内部是通过队列结合线程实现的,当我们利用线程池执行任务时: ...

  6. C#多线程之线程同步篇3

    在上一篇C#多线程之线程同步篇2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier ...

  7. C#多线程之线程同步篇2

    在上一篇C#多线程之线程同步篇1中,我们主要学习了执行基本的原子操作.使用Mutex构造以及SemaphoreSlim构造,在这一篇中我们主要学习如何使用AutoResetEvent构造.Manual ...

  8. 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法

    [源码下载] 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法 作者:webabcd 介绍重新想象 Wi ...

  9. ExecutorService 建立一个多线程的线程池的步骤

    ExecutorService 建立一个多线程的线程池的步骤: 线程池的作用: 线程池功能是限制在系统中运行的线程数. 依据系统的环境情况,能够自己主动或手动设置线程数量.达到执行的最佳效果:少了浪费 ...

随机推荐

  1. Be Better:遇见更好的自己-2016年记

    其实并不能找到好的词语来形容过去的一年,感觉就如此平淡的过了!没有了毕业的稚气,看事情淡了,少了一丝浮躁,多了一分认真.2016也许就是那句话-多读书,多看报,少吃零食多睡觉,而我更愿意说--Be B ...

  2. SQL Server2014 SP2新增的数据库克隆功能

    SQL Server2014 SP2新增的数据库克隆功能 创建测试库 --创建测试数据库 create database testtest use testtest go --创建表 )) --插入数 ...

  3. Cassandra简介

    在前面的一篇文章<图形数据库Neo4J简介>中,我们介绍了一种非常流行的图形数据库Neo4J的使用方法.而在本文中,我们将对另外一种类型的NoSQL数据库——Cassandra进行简单地介 ...

  4. 说说Golang的使用心得

    13年上半年接触了Golang,对Golang十分喜爱.现在是2015年,离春节还有几天,从开始学习到现在的一年半时间里,前前后后也用Golang写了些代码,其中包括业余时间的,也有产品项目中的.一直 ...

  5. Unity游戏内版本更新

    最近研究了一下游戏内apk包更新的方法. ios对于应用的管理比较严格,除非热更新脚本,不太可能做到端内大版本包的更新.然而安卓端则没有此限制.因此可以做到不跳到网页或应用商店,就覆盖更新apk包. ...

  6. tomcat开发远程调试端口以及利用eclipse进行远程调试

    一.tomcat开发远程调试端口 方法1 WIN系统 在catalina.bat里:  SET CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compi ...

  7. 安装eclipse的maven插件

    我们团队用maven来管理项目需要的库文件,其实以前都没听过maven,第一次接触这个,师兄要我直接去装下这个,开始以为还挺简单的,没想到中间遇到了一些小麻烦,现在把我成功安装maven的过程分享下, ...

  8. MongoDB系列(一):简介及安装

    什么是MongoDB MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统. 在高负载的情况下,添加更多的节点,可以保证服务器性能. MongoDB 旨在为应用提供可扩展的高 ...

  9. Android—Volley:接收服务端发送的json数据乱码问题解决

    new JsonObjectRequest中重写方法parseNetworkResponse,内容如下: /** * 重写此方法不会导致乱码 */ @Override protected Respon ...

  10. 在开源中国(oschina)git中新建标签(tags)

    我今天提交代码到主干上面,本来想打个标签(tags)的. 因为我以前新建过标签(tags),但是我现在新建的时候不知道入库在哪了.怎么找也找不到了. 从网上找资料也没有,找客服没有人理我,看到一个交流 ...