C# 多线程编程
01 基本概念
多线程学习现状
多线程,很多人工作多年,几乎没有主动用过多线程,或者用过,但是面试说不清楚,而且内心里,特别没
底;网上资源虽多,看了觉得有道理,但是好像实践不起来
概念
进程:程序在服务器上运行时,占据的计算资源合集,称之为进程
进程之间不会相互干扰-一进程间的通信比较困难(分布式)
线程:程序执行的最小单位,响应操作的最小执行流,
线程也包含自己的计算资源,
线程是属于进程的,一个进程可以有多个线程
多线程:一个进程里面,有多个线程并发执行
C# 中的线程:
多线程Thread:类,就是一个封装,是.NetFramework对线程对象的抽象封装
通过Thread去完成的操作,最终是通过向操作系统请求得到的执行流
Current Thread:当前线程--任何操作执行都是线程完成的,运行当前这句话的线程
ManagedThreadld:是.Net平台给Thread起的名字,就是个int值,尽量不重复
异步操作和多线程的区别
多线程和异步操作的异同
多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。甚至有些时候我们就认为多线程和异步操作是等同的概念。但是,多线程和异步操作还是有一些区别的。而这些区别造成了使用多线程和异步操作的时机的区别。
异步操作的本质
所有的程序最终都会由计算机硬件来执行,所以为了更好的理解异步操作的本质,我们有必要了解一下它的硬件基础。 熟悉电脑硬件的朋友肯定对DMA这个词不陌生,硬盘、光驱的技术规格中都有明确DMA的模式指标,其实网卡、声卡、显卡也是有DMA功能的。DMA就是直接内存访问的意思,也就是说,拥有DMA功能的硬件在和内存进行数据交换的时候可以不消耗CPU资源。只要CPU在发起数据传输时发送一个指令,硬件就开始自己和内存交换数据,在传输完成之后硬件会触发一个中断来通知操作完成。这些无须消耗CPU时间的I/O操作正是异步操作的硬件基础。所以即使在DOS这样的单进程(而且无线程概念)系统中也同样可以发起异步的DMA操作。
线程的本质
线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。
异步操作的优缺点
因为异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些初入,而且难以调试。
多线程的优缺点
多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现。
适用范围
在了解了线程与异步操作各自的优缺点之后,我们可以来探讨一下线程和异步的合理用途。我认为:当需要执行I/O操作时,使用异步操作比使用线程+同步I/O操作更合适。I/O操作不仅包括了直接的文件、网络的读写,还包括数据库操作、Web Service、HttpRequest以及.Net Remoting等跨进程的调用。
而线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行。但是往往由于使用线程编程的简单和符合习惯,所以很多朋友往往会使用线程来执行耗时较长的I/O操作。这样在只有少数几个并发操作的时候还无伤大雅,如果需要处理大量的并发操作时就不合适了。
实例研究
说了那么理论上的东西,可能有些兄弟早就不耐烦了,现在我们来研究几个实际的异步操作例子吧。
实例1:由delegate产生的异步方法到底是怎么回事?
大家可能都知道,使用delegate可以"自动"使一个方法可以进行异步的调用。从直觉上来说,我觉得是由编译器或者CLR使用了另外的线程来执行目标方法。到底是不是这样呢?让我们来用一段代码证明一下吧。
using System;
using System.Threading;
namespace AsyncDelegateDemo
{
delegate void AsyncFoo(int i);
class Program
{
///<summary>
/// 输出当前线程的信息
///</summary>
///<param name="name">方法名称</param>
static void PrintCurrThreadInfo(string name)
{
Console.WriteLine("Thread Id of " + name+ " is: " + Thread.CurrentThread.ManagedThreadId+ ", current thread is "
+ (Thread.CurrentThread.IsThreadPoolThread ? "" : "not ")
+ "thread pool thread.");
}
///<summary>
/// 测试方法,Sleep一定时间
///</summary>
///<param name="i">Sleep的时间</param>
static void Foo(int i)
{
PrintCurrThreadInfo("Foo()");
Thread.Sleep(i);
}
///<summary>
/// 投递一个异步调用
///</summary>
static void PostAsync()
{
AsyncFoo caller = new AsyncFoo(Foo);
caller.BeginInvoke(1000, new AsyncCallback(FooCallBack), caller);
}
static void Main(string[] args)
{
PrintCurrThreadInfo("Main()");
for(int i = 0; i < 10 ; i++)
{
PostAsync();
}
Console.ReadLine();
}
static void FooCallBack(IAsyncResult ar)
{
PrintCurrThreadInfo("FooCallBack()");
AsyncFoo caller = (AsyncFoo) ar.AsyncState;
caller.EndInvoke(ar);
}
}
}
这段代码代码的输出如下:
Thread Id of Main() is: 1, current thread is not thread pool thread.
Thread Id of Foo() is: 3, current thread is thread pool thread.
Thread Id of FooCallBack() is: 3, current thread is thread pool thread.
Thread Id of Foo() is: 3, current thread is thread pool thread.
Thread Id of Foo() is: 4, current thread is thread pool thread.
Thread Id of Foo() is: 5, current thread is thread pool thread.
Thread Id of FooCallBack() is: 3, current thread is thread pool thread.
Thread Id of Foo() is: 3, current thread is thread pool thread.
Thread Id of FooCallBack() is: 4, current thread is thread pool thread.
Thread Id of Foo() is: 4, current thread is thread pool thread.
Thread Id of Foo() is: 6, current thread is thread pool thread.
Thread Id of FooCallBack() is: 5, current thread is thread pool thread.
Thread Id of Foo() is: 5, current thread is thread pool thread.
Thread Id of Foo() is: 7, current thread is thread pool thread.
Thread Id of FooCallBack() is: 3, current thread is thread pool thread.
Thread Id of Foo() is: 3, current thread is thread pool thread.
Thread Id of FooCallBack() is: 4, current thread is thread pool thread.
Thread Id of FooCallBack() is: 6, current thread is thread pool thread.
Thread Id of FooCallBack() is: 5, current thread is thread pool thread.
Thread Id of FooCallBack() is: 7, current thread is thread pool thread.
Thread Id of FooCallBack() is: 3, current thread is thread pool thread.
从输出可以看出,.net使用delegate来"自动"生成的异步调用是使用了另外的线程(而且是线程池线程)。
02 简单的例子
异步多线程
//异步多线程多会使用委托
private void btnAsync_Click(object sender, EventArgs e)
{
Console. WriteLine ();
Console. WriteLine ("******************btnAsync_Click 异步方法 start {0}********************",Thread. CurrentThread. ManagedThreadld);
Action<string> action = this.DoSomethingLong; //Action是C# 的一个内置的委托类型,表示返回值是void,参数是泛型T的委托;
action.Invoke ("btnAsync_Click_l"); //委托执行,同步
action("btnAsync_Click_2"); //委托执行,同步
action.Beginlnvoke ("btnAsync_Click_3", null, null) ; //委托执行,异步
Console.writeLine ("******************btnAsync_Click 异步方法 end {0}********************",Thread. CurrentThread. ManagedThreadld);
Console.WriteLine ();
)
private void DoSomethingLong(String a){
Console.WriteLine ("******************方法所在线程 {0}********************",Thread. CurrentThread. ManagedThreadld);
}
有个疑问,如果beginlnvoke上万次,理论上开启了多少个线程?
beginlnvoke本质是调用了ThreadPool里的线程,ThreadPool中线程是有上限的
假如想没上限的使用,可以用Thread,但电脑会死机
03 异步多线程特点
1.同步单线程方法卡界面------ 主(UI)线程忙于计算,所以不能响应
异步多线程方法不卡界面一一计算任务交给子线程,主(UI)线程已经闲置,可以响应别的操作
cs:按钮后能不卡死一上传文件不卡死
bs:用户注册发邮件/发短信/写日志
2.同步单线程方法慢―-因为只有一个线程计算
异步多线程方法快--因为多个线程并发计算
多线程就是用资源换性能,但并不是线性增长
1个线程13000毫秒 5个线程4269毫秒 性能只有3倍提升
a多线程的协调管理额外成本一--项目经理
b资源也有上限的一-5辆车只有3条道
线程并不是越多越好,
3.无序性一不可预测性
启动无序:几乎同一时间像操作系统请求线程,也是个需要CPU处理的请求
因为线程是操作系统资源,CLR只能去申请,具体是什么顺序,无法掌控
执行时间不确定:同一个线程同一个任务耗时也可能不同
其实跟操作系统的调度策略有关,
CPU分片(计算能力太强,Is分拆1000份儿,宏观上就变成了并发的)
那任务执行过程就得看运气了一--线程的优先级可以影响操作系统的调度
结束无序:以上加起来
04 简单控制线程顺序
1. 异步回调
委托异步调用后执行回调函数,回调函数是在子线程内执行的
AsyncCallback callback = ar =>
{
Console.WriteLine(ar.AsyncState);
Console.WriteLine("btnAsyncAdvanced_Click操作已经完成了。。。${Thread. CurrentThread. ManagedThreadld}");
};
//第二个参数是个委托,定义回调函数,第三个参数是各object是传递给回调的ar.AsyncState
action.Beginlnvoke("btnAsyncAdvanced_Click", callback, "sunday");
2. IsCompleted属性
希望一方面文件上传,完成后才预览;另一方面,还希望有个进度提示一只有主线程才能操作界面;
有等待,但会有时间上误差
IAsyncResult asyncResult = action.Beginlnvoke("文件上传",null, null);
int i = 0;
while(!asyncResult.IsCompleted) //该属性用来描述异步动作是否完成,其实一开始该属性就为false,等异步动作完成会会修改该属性为true
{
if(i < 9)
{
this.ShowConsoleAndView($"当前文件上传进度为{++i * 10}% ...");
}
else
{
this.ShowConsoleAndView($"当前文件上传进度为99. 999%...");
}
Thread.Sleep(200);
}
Console.WriteLine("完成文件上传,执行预览,绑定到界面");
private void ShowConsoleAndView(string text)
{
Console. WriteLine(text) ;
this.IblProcessing.Text = text; //实际过程中,只有上面的Console在实时更新,下面这句需要等到UI线程闲下来才能更新
}
3. 信号量
可以做超时,等待
var asyncResult = action.Beginlnvoke("调用接口",null, null);
Console. WriteLine ("Do Something Else......."); // 发起异步调用后,再用信号量阻塞当前线程和同步的区别就在这里,可以在中间做其他操作
Console. WriteLine ("Do Something Else........");
Console. WriteLine ("Do Something Else........");
Console. WriteLine ("Do Something Else.......");
Console. WriteLine ("Do Something Else........");
asyncResult.AsyncWaitHandle.WaitOne0 ;"阻塞当前线程,直到收到信号量,从asyncResult发出, 无延迟
//asyncResult. AsyncWaitHandle. WaitOne(-1) ;//一直等待
//asyncResult. AsyncWaitHandle. WaitOne (1500);"阻塞当前线程,等待且最多只等待1000ms,超过就过时了 这个就是用来做超时控制,微服务架构,一个操作需要调用5个接口,如果臬个接口很慢,会影响整个流程,可以做超时控制,超时就换接口或者放弃或者给个结果
Console. WriteLine ("接口调用成功,必须是真实的成功。。");
4. EndInvoke 异步调用获取返回值
调用远程接口获取返回值
本质也是信号量
public void main(){
Func<int> func = this.RemoteService; //Func也是各内置委托类
IAsyncResult asyncResult = func.Beginlnvoke(ar =>
{
int iResult4 = func.EndInvoke(ar);
}, null);//异步调用结果,描述异步操作的
//int iResult3 = func.Endlnvoke(asyncResult); //EndInvoke可以放在回调里,也可以放在主线程中,但只允许放在一个地方,不能两个同时存在
//如果想获取异步调用真实返回值,只能EndInvoke
/*{ //其他的Func委托类使用
Func<string> func2 = () => Dat eTime. Now. ToStringO ;
string sResult = func2. Endlnvoke(func2. Beginlnvoke(null, null));
}
{
Func<string, string> func2 = s => $〃l+{s}〃;
string sResult = func2. Endlnvoke (func2. Beginlnvoke(z,Jasonz,, null, null));
)*/
}
/// <summary>
///模拟的远程接口
/// </summary>
/// <returns></returns>
private int RemoteService()
{
long IResult = 0;
for (int i = 0; i < 1000000000; i++)
IResult += i;
return DareTime.Now.Day;
}
05 多线程发展历史
//NetFramework 1.0 1.1
ThreadStart threadStart = ()=>
{
Console. WriteLine ($"This is Thread Start{Thread. CurrentThread. ManagedThreadld}");
Thread.Sleep(2000);
Console. WriteLine ($"This is Thread End {Thread. CurrentThread. ManagedThreadld}");
};
Thread thread = new Thread(threadStart);
thread.Start ();
//thread. Suspend 0; 挂起线程
//thread. Resume (); 恢复线程
//thread. JoinO ; 线程等待
//thread. IsBackground 是否是后台线程
//thread. Abort (); 线程销毁
//Thread. ResetAbort(); 线程销毁恢复
//“Thread的API特别丰富,可以玩的很花哨,但是其实玩不好--因为线程资源是操作系统管理的,
//响应并不灵敏,所以没那么好控制
//“Thread启动线程是没有控制的,可能导致死机
//“Thread就很像给一个四岁的小孩一把热武器,威力很大,但是可能造成更大的破坏
//------------------------------------------------------------------------------------------------------
//. NetFramework 2.0(新的CLR) ThreadPool:池化资源管理设计思想,线程是一种资源,之前每 次要用线程,就去申请一个线程,使用完之后,释放掉;池化就是做一个容器,容器提前申请5 个线程,程序需要使用线程,直接找容器获取,用完后再放回容器(控制状态),避免频繁的申 请和销毁;容器自己还会根据限制的数量去申请和释放;
//I线程复用2可以限制最大线程数量
WaitCallback callback = o =>
{
Console.WriteLine($"This is ThreadPool Start{Thread. CurrentThread. ManagedThreadld}");
Thread.Sleep (2000);
Console.WriteLine ($"This is ThreadPool End{Thread. CurrentThread. ManagedThreadld}");
}
ThreadPool.QueueUserWorkltem(callback);
//ThreadPool. set
//API又太少了,线程等待顺序控制特别弱,MRE,影响了实战
//------------------------------------------------------------------------------------------------------
//. NetFramework 3.0 Task被称之为多线程的最佳实践!,同时后续版本一直在升级Task 1 Task线程全部是线程池线程 2 提供了丰富的API,非常适合开发实践
Action action = 0 =>
{
Console.WriteLine ($"This is Task Start{Thread. CurrentThread. ManagedThreadld}");
Thread.Sleep(2000);
Console.WriteLine ($"This is Task End {Thread.CurrentThread.ManagedThreadld}");
}
Task task = new Task(action);
task.Start();
//------------------------------------------------------------------------------------------------------
//并行编程,Parallel可以启动多线程,主线程也参与计算,节约一个线程;
//可以通过Paralleloptions轻松控制最大并发数量,应用场景工作流转出时的处理
Parallel.Invoke(0 =>
{
Console.WriteLine ($"This is Task Start{Thread. CurrentThread. ManagedThreadld}");
Thread.Sleep(2000);
Console.WriteLine ($"This is Task End {Thread.CurrentThread.ManagedThreadld}");
},0 =>
{
Console.WriteLine ($"This is Task Start{Thread. CurrentThread. ManagedThreadld}");
Thread.Sleep(2000);
Console.WriteLine ($"This is Task End {Thread.CurrentThread.ManagedThreadld}");
})
//------------------------------------------------------------------------------------------------------
//async await 本质是个语法糖
PS:委托的beginInvoke在.net core 下不支持
06 Task专题
简单体验
//除了明白场景,还要知道线程问题,
//下面用多线程是为了提升效率,有严格时间限制的,先后顺序的,只能单线程,因为这些任务是可以独立并发执行的
//一个数据库查询 10 条数据需要 10s ,能不能多线程优化? 不能,任务不能分割
//一个操作要查询数据库,要调用接口,要读硬盘文件?可以,任务之间相互独立
Console.WriteLine("大家开始干活了");
List<Task> tasks = new List<Task>();
tasks.add(Task.Run(() => this.Coding(‘小明’,"开发Portal")));
tasks.add(Task.Run(() => this.Coding(‘小红’,"开发WebApi")));
tasks.add(Task.Run(() => this.Coding(‘小海’,"开发前台")));
//阻塞当前线程,直到任一任务完成
Task.WaitAny(tasks.toArray());
Console.WriteLine("活干好一部分了");
//阻塞当前线程,直到所有任务完成
Task.WaitAll(tasks.toArray()); //等待线程执行完
Console.WriteLine("活干好了");
PS:尽量不要线程套线程
上面的Task.WaitAny和Task.WaitAll都会阻塞线程,使用如下方式就可以解决
TaskFactory tf = new TaskFactory();
//等待任一任务完成后,启动一个新的task来完成后续动作
tf.ContinueWhenAny(tasks.toArray(),t=>{
Console.WriteLine("活干好一部分了");
})
//等待所有任务完成后,启动一个新的task来完成后续动作
tf.ContinueWhenAll(tasks.toArray(),t=>{
Console.WriteLine("活干好了");
})
//continue的后续线程,可能是新线程,也可能是刚完成任务的线程,还可能是同一个线程,但不可能是主线程
task执行完后回调
Task task = Task.Run(()=>{
Console.WriteLine("线程干活");
})
//执行回调
task.ContinueWith(t=>{
Console.WriteLine("线程干完活了");
})
07 线程安全
定义:一段代码,单线程执行和多线程执行结果不同,就是线程安全问题
一个简单的线程安全演示
Console.WriteLine($"这是主线程启动了,主线程id:{Thread.CurrentThread.ManagedThreadId}");
for(int i = 0; i < 5; i++)
{
Task.Run(()=> {
Console.WriteLine($"这是{i}启动了,当前线程id:{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"这是{i}结束了,当前线程id:{Thread.CurrentThread.ManagedThreadId}");
});
}
Thread.Sleep(3000);
Console.WriteLine($"这是主线程结束了,主线程id:{Thread.CurrentThread.ManagedThreadId}");
输出结果:
这是主线程启动了,主线程id:1
这是4启动了,当前线程id:5
这是4启动了,当前线程id:6
这是5启动了,当前线程id:4
这是5启动了,当前线程id:9
这是5启动了,当前线程id:10
这是5结束了,当前线程id:5
这是5结束了,当前线程id:6
这是5结束了,当前线程id:9
这是5结束了,当前线程id:4
这是5结束了,当前线程id:10
这是主线程结束了,主线程id:1
上面有个bug:为什么打印出来的结果显示的是1个4和4个5,而不是1、2、3、4、5?
因为Task.run只是创建线程,并让线程处于就绪状态,至于什么时候执行线程,是CPU调度的问题,线程执行的时候i的值是并不是我们想象中的1、2、3、4、5,而且所有的线程都共享同一个i,
上面的程序小改下:
Console.WriteLine($"这是主线程启动了,主线程id:{Thread.CurrentThread.ManagedThreadId}");
for(int i = 0; i < 5; i++)
{
int k = i;
Task.Run(()=> {
Console.WriteLine($"这是{i}启动了,当前线程id:{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"这是{i}结束了,当前线程id:{Thread.CurrentThread.ManagedThreadId}");
});
}
Thread.Sleep(3000);
Console.WriteLine($"这是主线程结束了,主线程id:{Thread.CurrentThread.ManagedThreadId}");
这时候输出结果为:
这是主线程启动了,主线程id:1
这是5,0启动了,当前线程id:6
这是5,3启动了,当前线程id:8
这是5,1启动了,当前线程id:5
这是5,4启动了,当前线程id:7
这是5,2启动了,当前线程id:4
这是5,0结束了,当前线程id:6
这是5,1结束了,当前线程id:5
这是5,4结束了,当前线程id:7
这是5,2结束了,当前线程id:4
这是5,3结束了,当前线程id:8
这是主线程结束了,主线程id:1
我们发现i还是像上面说的一样,但是k却按照1、2、3、4、5的顺序来了,这又是为什么呢?
因为所有的线程都共享一个i,但所有的线程并不共享k,假如把int k的声明放到for循环外面则是共享同一个k了,作用域的问题
另一个简单的线程安全演示
List<int> list = new List<int>();
for (int i = 0; i < 10000; i++) {
Task.Run(() => {
list.Add(i);
});
}
Thread.Sleep(5000);
Console.WriteLine($"list集合长度{list.Count}");
这时候输出结果为:
list集合长度9939
这就有问题了,为什么不是10000?
因为list本身是个数组,在内存上是连续摆放的,假如同一时刻,去增加一个数据,都是操作同一个内存未知,2个cpu同时发了命令,内存先执行一个再执行一个,就出现覆盖
Lock
解决上面问题,有个简单粗暴的方法,使用lock
static void Main(string[] args)
{
List<int> list = new List<int>();
for (int i = 0; i < 10000; i++)
{
Task.Run(() => {
lock(LOCK)
{
list.Add(i);
}
});
}
Thread.Sleep(5000);
Console.WriteLine($"list集合长度{list.Count}");
}
private static readonly object LOCK = new object();
加lock就能解决线程安全问题--就是单线程化--Lock就是保证方法块儿任意时刻只有一个线程能进去,其他线程就排队一一单线程化
lock关键字,本质是个语法糖,
lock(LOCK){
}
//等价于
Monitor.Enter(LOCK);
{
}
Monitor.Exit(LOCK);
意思是占据一个引用,在内存中,引用类型是存放在堆里的,线程资源栈里存放指向该堆地址的指针,lock就是占据该引用,因此lock不能用数值类型,也不能用null
假如lock的是字符串,字符串值一样,那么线程间也是相互阻塞的,因为字符串在C#中是享元的,在堆里是同一个,即使变量不一样,但他们指向的地址都是同一个,lock锁的是引用
08 await/async
初识
/// <summary>
/// await/async:是个新语法,出现C#5.0 .NetFramework在4.5及以上(CLR4.0)
/// 是一个语法糖,不是一个全新的异步多线程使用方式,
/// (语法糖:就是编译器提供的新功能)
/// 本身并不会产生新的线程,但是依托于Task而存在,所以程序执行时也是有多线程的
/// </summary>
public class AwaitAsyncClassNew
{
public async Task DoSomething()
{
await Task.Run(()=>
Console.WiiteLine("*************");
})
}
初识2
用了async/await 之后,原本没有返回值的,可以返回Task,原本有返回值T的,可以返回Task
await关键字后面的代码,相当于是await task的回调
namespace async和await演示1
{
public class AsyncAndAwaitDemo
{
public void Show()
{
Console.WriteLine($"Show 方法Start,所属线程:{Thread.CurrentThread.ManagedThreadId}");
NoReturn2();
Console.WriteLine($"Show 方法End,所属线程:{Thread.CurrentThread.ManagedThreadId}");
}
#region 没有返回值的
/// <summary>
/// 一个普通的多线程
/// </summary>
public void NoReturn()
{
Console.WriteLine($"NoReturn 方法Start,所属线程:{Thread.CurrentThread.ManagedThreadId}");
Task.Run(() =>
{
Console.WriteLine($"NoReturn 方法 Task Start,所属线程:{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
Console.WriteLine($"NoReturn 方法 Task End,所属线程:{Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine($"NoReturn 方法End,所属线程:{Thread.CurrentThread.ManagedThreadId}");
}
/// <summary>
/// 原本有返回值T的,可以返回Task<T>
/// </summary>
/// <returns></returns>
public async Task NoReturn2()
{
Console.WriteLine($"NoReturn 方法Start,所属线程:{Thread.CurrentThread.ManagedThreadId}");
//调用线程创建新线程执行内部操作
Task task = Task.Run(() =>
{
Console.WriteLine($"NoReturn 方法 Task Start,所属线程:{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
Console.WriteLine($"NoReturn 方法 Task End,所属线程:{Thread.CurrentThread.ManagedThreadId}");
});
//让调用线程回去忙自己的事情,用了await后,相当于将await后面的代码包装成一个回调,可以用同步编码的形式来写异步
await task;
Console.WriteLine($"NoReturn 方法End,所属线程:{Thread.CurrentThread.ManagedThreadId}");
}
#endregion
#region 带返回值的
public long ReturnLong()
{
Console.WriteLine($"ReturnLong 方法Start,所属线程:{Thread.CurrentThread.ManagedThreadId}");
long result = 0;
Task.Run(() =>
{
Console.WriteLine($"ReturnLong 方法 Task Start,所属线程:{Thread.CurrentThread.ManagedThreadId}");
for (int i = 0; i < 100000000; i++)
{
result++;
}
Console.WriteLine($"ReturnLong 方法 Task End,所属线程:{Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine($"ReturnLong 方法End,所属线程:{Thread.CurrentThread.ManagedThreadId}");
return result;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public async Task<long> ReturnLong2()
{
Console.WriteLine($"ReturnLong2 方法Start,所属线程:{Thread.CurrentThread.ManagedThreadId}");
long result = 0;
await Task.Run(() =>
{
Console.WriteLine($"ReturnLong2 方法 Task Start,所属线程:{Thread.CurrentThread.ManagedThreadId}");
//for (int i = 0; i < 100000000; i++)
//{
// result++;
//}
Console.WriteLine($"ReturnLong2 方法 Task End,所属线程:{Thread.CurrentThread.ManagedThreadId}");
//return result;
});
Console.WriteLine($"ReturnLong2 方法End,所属线程:{Thread.CurrentThread.ManagedThreadId}");
return result;
}
#endregion
}
}
09 其他
死锁、STAThread等