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等

原文地址:https://www.cnblogs.com/zhenhunfan2/p/14447496.html