二、.NET中的多线程编程

2.1 如何在.NET程序中手动控制多个线程?

   最直接且灵活性最大的,莫过于主动创建、运行、结束所有线程。

  (1)第一个多线程程序

  .NET提供了非常直接的控制线程类型的类型:System.Threading.Thread类。下面是一个简单的多线程程序:

    class Program
{
static void Main(string[] args)
{
Console.WriteLine("进入多线程工作模式:");
for (int i = ; i < ; i++)
{
Thread newThread = new Thread(Work);
// 开启新线程
newThread.Start();
} Console.ReadKey();
} static void Work()
{
Console.WriteLine("线程开始");
// 模拟做了一些工作,耗费1s时间
Thread.Sleep();
Console.WriteLine("线程结束");
}
}

  在主线程中,该代码创建了10个新的线程,这个10个线程的工作互不干扰,宏观上来看它们应该是并行运行的,执行的结果也证实了这一点:

  

PS:当new了一个Thread类型对象并不意味着生成了一个线程,线程的生成是在调用Thread的Start方法的时候。另外这里的线程并不一定是操作系统层面上产生的一个真正线程!

  (2)控制线程的状态

  在任意时刻,.NET中的线程都会处于如下图所示的几个状态中的某一个状态上,该图也直观地展示了一个线程可能经过的状态转换过程

  下面的示例代码则展示了我们如何手动地查看和控制一个线程的状态:

    class Program
{
static void Main(string[] args)
{
Console.WriteLine("开始测试线程1");
// 初始化一个线程 thread1
Thread thread1 = new Thread(Work1);
// 这时状态:UnStarted
PrintState(thread1);
// 启动线程
Console.WriteLine("现在启动线程");
thread1.Start();
// 这时状态:Running
PrintState(thread1);
// 让线程飞一会 3s
Thread.Sleep( * );
// 让线程挂起
Console.WriteLine("现在挂起线程");
thread1.Suspend();
// 给线程足够的时间来挂起,否则状态可能是SuspendRequested
Thread.Sleep();
// 这时状态:Suspend
PrintState(thread1);
// 继续线程
Console.WriteLine("现在继续线程");
thread1.Resume();
// 这时状态:Running
PrintState(thread1);
// 停止线程
Console.WriteLine("现在停止线程");
thread1.Abort();
// 给线程足够的时间来终止,否则的话可能是AbortRequested
Thread.Sleep();
// 这时状态:Stopped
PrintState(thread1);
Console.WriteLine("------------------------------");
Console.WriteLine("开始测试线程2");
// 初始化一个线程 thread2
Thread thread2 = new Thread(Work2);
// 这时状态:UnStarted
PrintState(thread2);
// 启动线程
thread2.Start();
Thread.Sleep( * );
// 这时状态:WaitSleepJoin
PrintState(thread2);
// 给线程足够的时间结束
Thread.Sleep( * );
// 这时状态:Stopped
PrintState(thread2); Console.ReadKey();
} // 普通线程方法:一直在运行从未被超越
private static void Work1()
{
Console.WriteLine("线程运行中...");
// 模拟线程运行,但不改变线程状态
// 采用忙等状态
while (true) { }
} // 文艺线程方法:运行10s就结束
private static void Work2()
{
Console.WriteLine("线程开始睡眠:");
// 睡眠10s
Thread.Sleep( * );
Console.WriteLine("线程恢复运行");
} // 打印线程的状态
private static void PrintState(Thread thread)
{
Console.WriteLine("线程的状态是:{0}", thread.ThreadState.ToString());
}
}

  上述代码的执行结果如下图所示:

PS:在.NET Framework 4.0 及之后的版本中,已不鼓励使用线程的挂起状态及Suspend和Resume方法了。

2.2 如何使用.NET中的线程池?

  (1).NET中的线程池是神马

  线程的创建和销毁需要很大的性能开销,在Windows NT内核的操作系统中,每个进程都会包含一个线程池。

而在.NET中呢,也有自己的线程池,它是由CLR负责管理的。

  线程池相当于一个缓存的概念,在该池中已经存在了一些没有被销毁的线程,而当应用程序需要一个新的线程时,就可以从线程池中直接获取一个已经存在的线程。相对应的,当一个线程被使用完毕后并不会立刻被销毁,而是放入线程池中等待下一次使用

PS:线程池中运行的线程均为后台线程(即线程的 IsBackground 属性被设为true),所谓的后台线程是指这些线程的运行不会阻碍应用程序的结束。相反的,应用程序的结束则必须等待所有前台线程结束后才能退出。

  (2)在.NET中使用线程池

  在.NET中通过 System.Threading.ThreadPool 类型来提供关于线程池的操作,ThreadPool 类型提供了几个静态方法允许使用者插入一个工作线程的需求。常用的有以下三个静态方法:

  ① static bool QueueUserWorkItem(WaitCallback callback)

  ② static bool QueueUserWorkItem(WaitCallback callback, Object state)

  ③ static bool UnsafeQueueUserWorkItem(WaitCallback callback, Object state)

  有了这几个方法,我们只需要将线程要处理的方法作为参数传入上述方法即可,随后的工作都由CLR的线程池管理程序来完成。其中,WaitCallback 是一个委托类型,该委托方法接受一个Object类型的参数且没有返回值。下面的代码展示了如何使用线程池来编写多线程的程序:

    class Program
{
static void Main(string[] args)
{
string taskInfo = "运行10秒";
// 插入一个新的请求到线程池
bool result = ThreadPool.QueueUserWorkItem(DoWork, taskInfo);
// 分配线程有可能会失败
if (!result)
{
Console.WriteLine("分配线程失败");
}
else
{
Console.WriteLine("按回车键结束程序");
} Console.ReadKey();
} private static void DoWork(object state)
{
// 模拟做了一些操作,耗时10s
for (int i = ; i < ; i++)
{
Console.WriteLine("工作者线程的任务是:{0}", state);
Thread.Sleep();
}
}
}

  上述代码执行后,如果不输入任何字符,那么会得到如下图所示的执行结果:

PS:事实上,UnsafeQueueWorkItem方法实现了完全相同的功能,二者的差别在于UnsafeQueueWorkItem方法不会将调用线程的堆栈传递给辅助线程,这就意味着主线程的权限限制不会传递给辅助线程。UnsafeQueueWorkItem由于不进行这样的传递,因此会得到更高的运行效率,但是潜在地提升了辅助线程的权限,也就有可能会成为一个潜在的安全漏洞。

2.3 如何查看和设置线程池的上下限?

  通常情况下,我们无需修改默认的配置。但在一些场合,我们可能需要了解线程池的上下限和剩余的线程数。

线程池作为一个缓冲池,有着其上下限。在通常情况下,当线程池中的线程数小于线程池设置的下限时,线程池会设法创建新的线程,而当线程池中的线程数大于线程池设置的上限时,线程池将销毁多余的线程

PS:在.NET Framework 4.0中,每个CPU默认的工作者线程数量最大值为250个,最小值为2个。而IO线程的默认最大值为1000个,最小值为2个。

  在.NET中,通过 ThreadPool 类型提供的5个静态方法可以获取和设置线程池的上限和下限,同时它还额外地提供了一个方法来让程序员获知当前可用的线程数量,下面是这五个方法的签名:

  ① static void GetMaxThreads(out int workerThreads, out int completionPortThreads)

  ② static void GetMinThreads(out int workerThreads, out int completionPortThreads)

  ③ static bool SetMaxThreads(int workerThreads, int completionPortThreads)

  ④ static bool SetMinThreads(int workerThreads, int completionPortThreads)

  ⑤ static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)

  下面的代码示例演示了如何查询线程池的上下限阈值和可用线程数量:

    class Program
{
static void Main(string[] args)
{
// 打印阈值和可用数量
GetLimitation();
GetAvailable(); // 使用掉其中三个线程
Console.WriteLine("此处申请使用3个线程...");
ThreadPool.QueueUserWorkItem(Work);
ThreadPool.QueueUserWorkItem(Work);
ThreadPool.QueueUserWorkItem(Work); Thread.Sleep(); // 打印阈值和可用数量
GetLimitation();
GetAvailable();
// 设置最小值
Console.WriteLine("此处修改了线程池的最小线程数量");
ThreadPool.SetMinThreads(, );
// 打印阈值
GetLimitation(); Console.ReadKey();
} // 运行10s的方法
private static void Work(object o)
{
Thread.Sleep( * );
} // 打印线程池的上下限阈值
private static void GetLimitation()
{
int maxWork, minWork, maxIO, minIO;
// 得到阈值上限
ThreadPool.GetMaxThreads(out maxWork, out maxIO);
// 得到阈值下限
ThreadPool.GetMinThreads(out minWork, out minIO);
// 打印阈值上限
Console.WriteLine("线程池最多有{0}个工作者线程,{1}个IO线程", maxWork.ToString(), maxIO.ToString());
// 打印阈值下限
Console.WriteLine("线程池最少有{0}个工作者线程,{1}个IO线程", minWork.ToString(), minIO.ToString());
Console.WriteLine("------------------------------------");
} // 打印可用线程数量
private static void GetAvailable()
{
int remainWork, remainIO;
// 得到当前可用线程数量
ThreadPool.GetAvailableThreads(out remainWork, out remainIO);
// 打印可用线程数量
Console.WriteLine("线程池中当前有{0}个工作者线程可用,{1}个IO线程可用", remainWork.ToString(), remainIO.ToString());
Console.WriteLine("------------------------------------");
}
}

  该实例的执行结果如下图所示:

PS:上面代码示例在不同的计算机上运行可能会得到不同的结果,线程池中的可用数码不会再初始时达到最大值,事实上CLR会尝试以一定的时间间隔来逐一地创建新线程,但这个时间间隔非常短。

.NET基础拾遗(7)多线程开发基础2的更多相关文章

  1. .NET基础拾遗(5)多线程开发基础

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  2. .NET基础拾遗(7)多线程开发基础4

    一.多线程编程中的线程同步 1.C#中的lock关键字 lock关键字可能是我们在遇到线程同步的需求时最常用的方式,但lock只是一个语法糖,为什么这么说呢,下面慢慢道来. (1)lock的等效代码其 ...

  3. .NET基础拾遗(7)多线程开发基础1

    一.多线程编程的基本概念 1.1 操作系统层面的进程和线程 (1)进程 进程代表了操作系统上运行着的一个应用程序.进程拥有自己的程序块,拥有独占的资源和数据且可以被操作系统调度. But,即使是同一个 ...

  4. .NET基础拾遗(7)多线程开发基础3

    一.如何使用异步模式? 异步模式是在处理流类型时经常采用的一种方式,其应用的领域相当广阔,包括读写文件.网络传输.读写数据库,甚至可以采用异步模式来做任何计算工作.相对于手动编写线程代码,异步模式是一 ...

  5. (转).NET基础拾遗(5)多线程开发基础

    https://www.cnblogs.com/edisonchou/p/4848131.html

  6. ios多线程开发基础

    多线程编程:下载数据时,开辟子线程,减少阻塞时间,和主线程并发运行,提升用户体验 1.Thread 1>新建Thread对象,带一selector方法,调用start方法,开启子线程 2> ...

  7. .NET基础拾遗(6)ADO.NET与数据库开发基础

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开发基 ...

  8. .NET基础拾遗(7)Web Service的开发与应用基础

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开发基 ...

  9. .NET基础拾遗(1)类型语法基础和内存管理基础

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开发基 ...

随机推荐

  1. 函数递归时,递归次数到900多时,就是抛出异常exception RuntimeError('maximum recursion depth exceeded',)

    import subprocess import multiprocessing import urllib import sys import os import pymongo import si ...

  2. 3.3.2 嵌入汇编(摘自<linux内核完全剖析>)

    内核C语言程序嵌入式汇编代码又叫内联汇编,具有输入和输出参数的嵌入汇编语句的基本格式为: ************************************************** asm( ...

  3. PHPCMS V9二次开发便捷自定义后台入口文件夹

    phpcms v9二次开发便捷自定义后台入口文件夹 最新发布的phpcms v9由于采用了mvc的设计模式,所以它的后台访问地址是固定的,虽然可以通过修改路由配置文件来实现修改,但每次都修改路由配置文 ...

  4. mysql 分区信息查看

    select partition_name part,partition_expression expr,partition_description descr,table_rows from INF ...

  5. 字符串匹配算法之Sunday算法

    字符串匹配查找算法中,最着名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore).两个算法在最坏情况下均具有线性的查找时间.但是在实用上,KMP算法并不比最简 ...

  6. Oracle database启动过程分析

    实例跟数据库的区别 实例(instance)是内存中的一块区域和一组后台进程的集合.它的作用是维护数据库文件的.而数据库(database)则是指存放数据的数据库文件.它是一系列格式化的数据的集合.它 ...

  7. 运行批处理bat文件不出现黑框

    .bat批处理文件运行时的cmd窗口是无法隐藏的,如果必须隐藏,需要采取间接的方法: 使用bat转exe的软件,有些软件可以在转换过程中让程序在后台运行: 使用vbs来运行bat,代码如下: DIM ...

  8. iPhone开发:Objective C 代码规范-iOS总结版

    一,关于空行 A:.h中的空行 1,文件说明与头文件包涵(#import)之间空1行 2,头文件包涵(#import)之间,如果需要分类区别,各类别之间空1行 3,头文件包涵(#import)与@cl ...

  9. java入门时的一些基本概念的理解(j2ee,j2se,j2me,jdk,sdk,jre,jvm,跨平台)

    首先声明,这篇文章是从网上粘贴过来的.原文地址是:http://www.cnblogs.com/wangaohui/archive/2012/11/28/2791999.html.感觉写的很好,所以粘 ...

  10. 使用john破解ubuntu(linux)9.10密码

    Title:使用john破解ubuntu(linux)9.10密码 --2011-11-23 15:00 ubuntu 9.10的账户密码加密方式改用sha512了,默认的john是破不了的,还好官方 ...