在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. NiceMark——我的Markdown编辑器

    NiceMark--我的Markdown编辑器 闲来无事,写了一个Markdown编辑器.基于electron,完全采用Web前段技术(Html,css,JavaScript)实现.代码已托管在Git ...

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

    在C#多线程之线程池篇中,我们将学习多线程访问共享资源的一些通用的技术,我们将学习到以下知识点: 在线程池中调用委托 在线程池中执行异步操作 线程池和并行度 实现取消选项 使用等待句柄和超时 使用计时 ...

  3. Hawk 6. 编译和扩展开发

    Hawk是开源项目,因此任何人都可以为其贡献代码.作者也非常欢迎使用者能够扩展出更有用的插件. 编译 编译需要Visual Stuido,版本建议使用2015, 2010及以上没有经过测试,但应该可以 ...

  4. kafka源码分析之一server启动分析

    0. 关键概念 关键概念 Concepts Function Topic 用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上. Partition 是Kafka中横向扩展和一 ...

  5. DataTable 转换成 Json的3种方法

    在web开发中,我们可能会有这样的需求,为了便于前台的JS的处理,我们需要将查询出的数据源格式比如:List<T>.DataTable转换为Json格式.特别在使用Extjs框架的时候,A ...

  6. SEED实验系列文章目录

    美国雪城大学SEEDLabs实验列表 SEEDLabs是一套完整的信息安全实验,涵盖本科信息安全教学中的大部分基本原理.项目组2002年由杜文亮教授创建,目前开发了30个实验,几百所大学已采用.实验楼 ...

  7. NYOJ 998

    这道题是欧拉函数的使用,这里简要介绍下欧拉函数. 欧拉函数定义为:对于正整数n,欧拉函数是指不超过n且与n互质的正整数的个数. 欧拉函数的性质:1.设n = p1a1p2a2p3a3p4a4...pk ...

  8. 打造TypeScript的Visual Studio Code开发环境

    打造TypeScript的Visual Studio Code开发环境 本文转自:https://zhuanlan.zhihu.com/p/21611724 作者: 2gua TypeScript是由 ...

  9. Promise的前世今生和妙用技巧

    浏览器事件模型和回调机制 JavaScript作为单线程运行于浏览器之中,这是每本JavaScript教科书中都会被提到的.同时出于对UI线程操作的安全性考虑,JavaScript和UI线程也处于同一 ...

  10. 使用CSS3的box-shadow实现双透明遮罩层对话框

    box-shadow介绍 在我之前的一篇文章<从天猫和支付宝身上学习opcity与rgba>中,介绍了实现双透明遮罩层效果的两种方法,分别是opacity和rgba.他们需要分别依赖于不同 ...