c# 多线程——入门学习
1. 概念介绍
1.1 线程
线程是操作系统能够进行运算调度的最小单位,包含在进程之中,是进程中的实际运作单位。一条线程指的时进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。.NET 中System.Thread下可以创建线程。
1.2 主线程
每个windows进程都包含一个用做程序入口点的主线程。进程入口点(main方法)中创建的第一个线程称为主线程,调用main方法时,主线程被创建。
1.3 前台线程
默认情况下,Thread.Start()方法创建的线程都是前台线程,属性isBackground=true/false能够设置线程的线程是否为后台线程。前台线程能阻止应用程序的终结,只有所有的前台线程执行完毕,CLR(Common Language Runtime,公共语言运行库)才能关闭应用程序。前台线程属于工作者线程。
1.4 后台线程
后台线程通过isBackground设置,它不会影响应用程序的终结,当所有前台线程执行完毕后,后台线程无论是否执行完毕,都会被终结。一般后台线程用来做无关紧要的任务(如邮箱天气更新等),后台线程也属于工作者线程。
2.多线程实现
2.1 创建线程
在VS2019中,建立一个控制台应用程序,测试多线程服务。首先开启2个线程workThread、printThread,分别实现数字计数、打印字母。代码实现如下:
class Program
{
static void Main(string[] args)
{
//新建两个线程,单独运行
Thread workThread=new Thread(NumberCount);
Thread printThread=new Thread(printNumber);
workThread.Start();
printThread.Start();
Console.WriteLine("Hello World!");
} public static void NumberCount()
{
for (int i = ; i < ; i++)
{
Console.WriteLine("the number is {0}",i);
}
} public static void printNumber()
{
for (char i = 'A'; i < 'J'; i++)
{ Console.WriteLine("print character {0}", i);
}
}
}
运行结果如下:
根据上述运行结果可以看出,主线程workThread和其他线程printThread运行时相互独立,互不干扰。
2.2 线程基本属性了解
static void Main(string[] args)
{
Thread th = Thread.CurrentThread;//访问当前正在运行的线程
bool aliveRes=th.IsAlive;//当前线程的执行状态
Console.WriteLine("IsAlive= {0}", aliveRes);
th.IsBackground =false;//线程是否为后台线程
Console.WriteLine("IsBackground= {0}", th.IsBackground);
bool isPool= th.IsThreadPoolThread;//当前线程是否属于托管线程池
Console.WriteLine("isPool= {0}", isPool);
int sysbol = th.ManagedThreadId;//获取当前托管线程的唯一标识
Console.WriteLine("ManagedThreadId= {0}", sysbol);
ThreadPriority pry=th.Priority;//设置线程调度优先级
Console.WriteLine("pry= {0}", pry);
ThreadState state=th.ThreadState;//获取当前线程状态值
Console.WriteLine("state= {0}", state);
th.Name = "main thread";
Console.WriteLine("this is {0}",th.Name);
Console.ReadKey();
Console.WriteLine("Hello World!");
}
2.3 暂停线程
暂停线程通过调用sleep()方法实现,使得线程暂停但不占用计算机资源,实现代码如下:
static void NumberCountCouldDelay()
{
for (int i = ; i < ; i++)
{
Console.WriteLine("the number is {0}", i);
Thread.Sleep(TimeSpan.FromSeconds());
}
}
public static void printNumber()
{
for (char i = 'A'; i < 'J'; i++)
{
Console.WriteLine("print character {0}", i);
Thread.Sleep(TimeSpan.FromSeconds());
}
}
运行结果如下:
2.4 线程池
线程池是一种多线程处理形式,将任务添加到队列,然后再创建线程后自动启动这些任务。通过线程池创建的任务属于后台任务,每个线程使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有的处理器保持繁忙。
实现代码及运行结果如下:
static void Main(string[] args)
{
Console.WriteLine("this is main thread: ThreadId={0}", Thread.CurrentThread.ManagedThreadId);
ThreadPool.QueueUserWorkItem(printNumber);
ThreadPool.QueueUserWorkItem(Go);
Console.Read();
} public static void printNumber(object data)
{
for (char i = 'A'; i < 'D'; i++)
{
Console.WriteLine("print character {0}", i);
Console.WriteLine("the print process threadId is {0}", Thread.CurrentThread.ManagedThreadId);
}
}
public static void Go(object data)
{
Console.Write("this is another thread:ThreadId={0}",Thread.CurrentThread.ManagedThreadId);
}
2.5 中止线程
线程中止采用abort方法,实现如下:
static void Main(string[] args)
{
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the child thread");
Thread childThread = new Thread(childref);//创建线程,扩展的Thread类
childThread.Start();//调用start()方法开始子线程的执行
//停止主线程一段时间
Thread.Sleep();
//现在中止子线程
Console.WriteLine("In Main: Abort the child thread");
childThread.Abort();
Console.WriteLine("Hello World!");
}
public static void CallToChildThread()
{
try
{
//调用abort()方法销毁线程
Console.WriteLine("Child thread start");
for (int counter=; counter<=;counter++)
{
Thread.Sleep();
Console.WriteLine(counter);
}
Console.WriteLine("child thread abort");
}
catch (ThreadAbortException e)
{
Console.WriteLine(e);
throw;
}
finally
{
Console.WriteLine("Couldn't catch the Thread Exception");
}
}
运行程序,出现如下错误:
经查找,发现.NET CORE平台不支持线程中止,在调用abort方法时会抛出ThreadAbortException异常。
2.5 跨线程访问
新建一个winform窗体应用程序,实现点击按钮为textbox赋值,代码如下:
private void Button1_Click(object sender, EventArgs e)
{
Thread thread=new Thread(test);
thread.IsBackground = true;
thread.Start();
Console.ReadLine();
} private void test()
{
for (int i = ; i < ; i++)
{
this.textBox1.Text = i.ToString();
}
}
然而,运行时出现以下错误,内容显示“线程间操作无效:从不是创建控件textBox1的线程访问它”。是因为控件textBox1是由主线程创建的,thread作为另外一个线程,在.NET上执行的是托管代码,c#强制要求代码线程安全,不允许跨线程访问。
上述问题解决办法如下:(参考https://docs.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls)
利用委托实现回调机制,回调过程如下:
(1)定义并声明委托;
(2)初始化回调方法;
(3)定义回调使用的方法
public partial class UserControl1: UserControl
{
private delegate void SetTextboxCallBack(int value);//定义委托 private SetTextboxCallBack setCallBack; /// <summary>
///定义回调使用的方法
/// </summary>
/// <param name="value"></param>
private void SetText(int value)
{
textBox1.Text = value.ToString();
}
public UserControl1()
{
InitializeComponent();
}
private void Button1_Click(object sender, EventArgs e)
{
//初始化回调函数
setCallBack=new SetTextboxCallBack(SetText);
//创建一个线程去执行这个回调函数要操作的方法
Thread thread = new Thread(test);
thread.IsBackground = true;
thread.Start();
Console.ReadLine();
}
public void test()
{
for (int i = ; i < ; i++)
{
//控件上执行回调方法,触发操作
textBox1.Invoke(setCallBack,i);
}
}
}
运行结果如下:
2.5 多线程使用委托
线程的创建通过new Thread来实现,c#中该构造函数的实现有以下4种:
- public Thread(ThreadStart start){}
- public Thread(ParameterizedThreadStart start){}
- public Thread(ThreadStart start, int maxStackSize){}
- public Thread(ParameterizedThreadStart start, int maxStackSize){}
其中,参数ThreadStart定义为:
public delegate void ThreadStart();//无参数无返回值的委托
参数ParameterizedThreadStart 定义为:
public delegate void ParameterizedThreadStart(object obj);//有参数无返回值的委托
因此,对无返回值的委托实现如下。
2.5.1 无参数无返回值的委托
对于无参数无返回值的委托,是最简单原始的使用方法。Thread thread= new Thread(new ThreadStart(()=>参数),其中参数为ThreadStart类型的委托。此类多线程代码实现如下:
class Program
{
public delegate void ThreadStart();//新建一个无参数、无返回值的委托
static void Main(string[] args)
{
Thread thread=new Thread(new System.Threading.ThreadStart(NumberCount));
thread.IsBackground = true;
thread.Start();
for (char i = 'A'; i < 'D'; i++)
{
Console.WriteLine("print character {0},the threadId id ={1}", i, Thread.CurrentThread.ManagedThreadId);
}
Console.WriteLine("Hello World!");
} public static void NumberCount()
{
for (int i = ; i < ; i++)
{
Console.WriteLine("the number is {0},the threadId id ={1}", i, Thread.CurrentThread.ManagedThreadId);
}
}
}
2.5.2 有参数无返回值的委托
对于有参数无返回值的委托,实现代码如下:
class Program
{
public delegate void ThreadStart(int i);//新建一个无参数、无返回值的委托
static void Main(string[] args)
{
Thread thread=new Thread(new ParameterizedThreadStart(NumberCount));
thread.IsBackground = true;
thread.Start(3);
for (char i = 'A'; i < 'D'; i++)
{
Console.WriteLine("print character {0},the threadId id ={1}", i, Thread.CurrentThread.ManagedThreadId);
}
Console.WriteLine("Hello World!");
} public static void NumberCount(object i)
{
Console.WriteLine("the number is {0},the threadId id ={1}", i, Thread.CurrentThread.ManagedThreadId);
} }
运行结果为:
2.5.2 有参数有返回值的委托
对于有参数有返回值的委托,采用异步调用实现,如下所示:
2.6 异步实现
2.6.1 Task.Result
..NET中引入了System.Threading.Tasks,简化了异步编程的方式,而不用直接和线程、线程池打交道。 System.Threading.Tasks中的类型被称为任务并行库(TPL),TPL使用CLR线程池(TPL创建的线程都是后台线程)自动将应用程序的工作动态分配到可用的CPU的中。
Result方法可以返回Task执行后的结果。但是在.NET CORE的webapi中使用result方法来获取task的输出值,会造成当前API线程阻塞等待到task执行完成后再继续。以下代码中,get方法中的线程id-57,调用一个新线程执行task后,等待TaskCaller()执行结果(threadid=59),待TaskCaller()方法执行完成后,原来的线程继续之后之后的语句,输出threadid=57
public class ValuesController:Controller
{
//async/await是用来进行异步调用的形式,
[HttpGet("get")]
public async Task<string> Get()
{
var info = string.Format("api执行线程{0}",Thread.CurrentThread.ManagedThreadId);//get方法中的线程
//调用新线程执行task任务
var infoTask = TaskCaller().Result;//调用result方法获取task的值
var infoTaskFinished = string.Format("api执行线程(taks调用completed){0}", Thread.CurrentThread.ManagedThreadId);
return string.Format("{0},{1},{2}", info, infoTask, infoTaskFinished);
}
public async Task<string> TaskCaller()
{
await Task.Delay();
return string.Format("task 执行线程{0}", Thread.CurrentThread.ManagedThreadId);
}
}
运行结果如下:
2.6.2 Async&Await
c#中async关键字用来指定方法,Lambda表达式或匿名方法自动以异步的方式来调用。async/await是用来进行异步调用的形式,内部采用线程池进行管理。如果使用await,在调用await tasjCall()是不会阻塞get方法的主线程,主线程会被释放,新的线程执行完task后继续执行await后的代码,从而减少了线程切换的开销,而之前的线程则空闲了。
public class ValuesAwaitController : Controller
{
[HttpGet("get")]
public async Task<string> Get()
{
var info = string.Format("api执行线程{0}",Thread.CurrentThread.ManagedThreadId);//get方法中的线程
//调用新线程执行task任务
var infoTask = await TaskCaller();//使用await调用不会阻塞Get()中线程
var infoTaskFinished = string.Format("api执行线程(taks调用completed){0}", Thread.CurrentThread.ManagedThreadId);
return string.Format("{0},{1},{2}", info, infoTask, infoTaskFinished);
}
public async Task<string> TaskCaller()
{
await Task.Delay();
return string.Format("task 执行线程{0}", Thread.CurrentThread.ManagedThreadId);
}
}
运行结果如下:
Task.result与await关键字具有类似的功能可以获取到任务的返回值,但本质上Task.result会让外层函数执行线程阻塞知道任务完成,而使用await外层函数线程不会阻塞,而是通过任务执行线程来执行await后的代码。
- 默认创建的Thread是前台线程,创建的Task为后台线程;
- ThreadPool创建的线程都是后台线程;
- 任务并行库(TPL)使用的是线程池计数;
- 调用async标记的方法,刚开始是同步执行,只有当执行到await标记的方法中的异步任务时,才会挂起。
c# 多线程——入门学习的更多相关文章
- java多线程入门学习(一)
java多线程入门学习(一) 一.java多线程之前 进程:每一个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销.一个进程包括1--n个线程. 线程:同一类线程共享代码 ...
- Android多线程入门学习
(1)进程间通信交换信息的一种方式--使用handler: (2)在主线程中new一个Handler对象,并重写他的handlerMessage(Message msg)方法: (3)Message中 ...
- Java多线程学习(一)Java多线程入门
转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79640870 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...
- Shiro learning - 入门学习 Shiro中的基础知识(1)
Shiro入门学习 一 .什么是Shiro? 看一下官网对于 what is Shiro ? 的解释 Apache Shiro (pronounced “shee-roh”, the Japanese ...
- Java入门学习路线目录索引
原创 Java入门学习路线目录索引 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/One_ ...
- Shiro入门学习与实战(一)
一.概述 1.Shiro是什么? Apache Shiro是java 的一个安全框架,主要提供:认证.授权.加密.会话管理.与Web集成.缓存等功能,其不依赖于Spring即可使用: Spring S ...
- RT-Thread移植入门学习
一.简介 RT-Thread 是一款主要由中国开源社区主导开发的开源实时操作系统(许可证GPLv2).实时线程操作系统不仅仅是一个单一的实时操作系统内核,它也是一个完整的应用系统,包含了实时.嵌入式系 ...
- JavaSE_多线程入门 线程安全 死锁 状态 通讯 线程池
1 多线程入门 1.1 多线程相关的概念 并发与并行 并行:在同一时刻,有多个任务在多个CPU上同时执行. 并发:在同一时刻,有多个任务在单个CPU上交替执行. 进程与线程 进程:就是操作系统中正在运 ...
- Reactive UI -- 反应式编程UI框架入门学习(二)
前文Reactive UI -- 反应式编程UI框架入门学习(一) 介绍了反应式编程的概念和跨平台ReactiveUI框架的简单应用. 本文通过一个简单的小应用更进一步学习ReactiveUI框架的 ...
随机推荐
- 法国:5G网络不会排除任何设备厂商
腾讯科技讯,据国外媒体报道,法国财政部长布鲁诺·勒梅尔(Bruno Le Maire)日前表示,法国有关 5G 电信网络的决定将基于网络的安全和性能,他强调说,法国政府不会将某一个特定的厂商排除在法国 ...
- redis3.2.2 集群
http://blog.csdn.net/imxiangzi/article/details/52431729 http://www.2cto.com/kf/201701/586689.html me ...
- 四十五、SAP中Message的管理
一.事务代码SE91 二.输入相关名字,点击创建 三.输入内容 四.定义成本地对象 五.在消息中添加一条短文本 六.我们代码如下 七.执行
- 干货分享:Academic Essay写作套路详解
你想过如何中立的表达自己吗?大概只有10%不到的同学,会真正重视这个细节.但很多留学生能顺利写完作文已经不容易,还要注意什么中立不中立的.我知道这个标准,对许多同学有些过分,但很残酷的告诉你,这的确是 ...
- 实验吧-杂项-MD5之守株待兔(时间戳&python时间戳函数time.time())
其实也有点蒙圈,因为从没做过和时间戳有关的题. 打开网站,将系统密钥解密得到一串值,而自己的密钥解密是空的,既然说是要和系统匹配,就把解密得到的值以get方式送出去. 但是发现还是在自己的密钥也发生了 ...
- 实验吧-密码学-他的情书(进一步了解js代码调试和console.log)
打开网站,在白色背景下的任一点上点击鼠标,白色部分都会消失(包括password输入框),那么就无法输入. 查看源码,发现是明显的从源码解决问题. 火狐F12查看器查看源码(如果是简单的操作,可以vi ...
- django ModelForm在模板中显示中文
情景再现 修改ModelForm 效果
- Marvolo Gaunt's Ring(巧妙利用前后缀进行模拟)
Description Professor Dumbledore is helping Harry destroy the Horcruxes. He went to Gaunt Shack as h ...
- PythonTwo
格式化输出: % 占位符 s(str 字符串) d(digit 数字) %% 只单纯显示% Str 索引切片 captlze 首字母大写 upper 全大写 lower 全小写 find 通过元 ...
- 【pwnable.kr】leg
pwnable从入门到放弃第八题. Download : http://pwnable.kr/bin/leg.cDownload : http://pwnable.kr/bin/leg.asm ssh ...