线程总是看,但却总是忘,今天跟着杨大佬的课,整理一下学习笔记,方便以后查阅。
创建线程、线程的一些相关操作
1. 线程的一些基本属性
方框表示进程,曲线表示线程。
CurrentThread:返回当前正在执行的线程,通过Name获取。
Console.WriteLine(Thread.CurrentThread.Name);
**
2. Thread.Join()&Thread.Sleep()
**
Join()例子:
class Program
{
static void Main(string[] args)
{
Thread t = new Thread(Go);//开票一个新的线程
t.Start();//运行go
t.Join();
Console.WriteLine("Thread t hsa ended!");
Console.ReadKey();
}
static void Go()
{
Console.WriteLine(Thread.CurrentThread.Name);
for (int i = 0; i < 1000; i++)
{
Console.Write("Y");
}
}
}
在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到由该实例表示的线程终止。
大概意思就是t线程运行完后才会执行后面的代码。
if (t.Join(2000))
Console.WriteLine("t has termminated.");
else
Console.WriteLine("The timeOut has elapsed and Thread1 will resume");
大概意思就是2秒后如果t还没执行完对应的方法体,就表示超时了,就执行else。否则相反。
Thread.Sleep(2000);
大概意思就是如果设置为0,当前线程将不在执行了,将会执行其他线程。
这个方法和上面的基本相同,都是放弃当前线程的执行。
3. 阻塞Blocking
不懂按位或按位与的点这里学习一下。
状态如下:
State状态图:
代码如下:
public static ThreadState SimpleThreadState(ThreadState ts)
{
return ts & (ThreadState.Unstarted | ThreadState.WaitSleepJoin | ThreadState.Stopped);
}
一下不懂按位或和按位与的给你们解释一下上面代码执行的逻辑。
ThreadState.Unstarted对应的上面状态码为8对应的二进制是00001000
ThreadState.StopRequested对应的状态码为1对应二进制为00000001
当Unstarted传入SimpleThreadState函数后,ts参数会与(ThreadState.Unstarted | ThreadState.WaitSleepJoin | ThreadState.Stopped)里面任意满足条件的进行按位与的操作,并都将转换对应的二进制,
00001000&00001000后还是00001000对应的十进制是8所以返回的是Unstarted
而00000001和函数里面的值进行按位与后还是00000000对应的10进制是0 所以 返回Running
阻塞解除相当于上下文切换
白话解释大概就是
I/O-bound相当于干等着,什么事都不干。
Compute-bound相当于一直占着CPU特别忙。
4. 什么是线程安全
本地独立的例子如下:大致意思就是2个线程独立调用了GO方法同时创建了2个cycles变量直译过来就是局部变量。
共享状态例子如下:
大概意思就是2个线程用了同一个实例化对象,所以共用了_done这个变量。
lambda例子和静态例子如下:
线程锁例子如下:
public class ThreadSafe
{
static bool _done;
static readonly object _locker = new object();
public static void Main1()
{
new Thread(go).Start();
go();
}
static void go()
{
lock (_locker) //加锁:每次只能一个线程进入
{
if (!_done)
{
Console.WriteLine("Done");
_done = true;
}
}
}
}
5. 向线程传递数据&异常处理
例子:
例子:
例子:
例子:
结果:
解决方案:
主线程是无法捕获到新建线程的异常的。
解决:
捕获放在新线程执行的方法里面就没问题了。
人话解释:就是在主线程上(就是UI层)有未处理的异常,上面那2个异常处理事件就会被触发。
6. 前台线程VS后台线程
例子:
解释:默认情况下我们创建的线程都是前台线程,在不传参数的情况下去执行,worker线程会一直等待着输入,因为是前台线程。
传入参数后,执行worker.IsBackground = true;
把worker线程设置为后台线程。当语句执行完后,程序就终止了,因为唯一的一个前台线程也设置为了后台线程,一旦所有前台线程都停止了那么应用程序也停止了。
7.线程优先级
8.信号简介
例子:
var signal = new ManualResetEvent(false); //定义信号
new Thread(() =>
{
Console.WriteLine("等待信号。。。");
signal.WaitOne();//阻塞线程 收到打开信号的命令后才会执行后面代码
signal.Dispose();
Console.WriteLine("收到信号!");
}).Start();
Thread.Sleep(3000);
signal.Set(); //打开信号
Console.ReadKey();
}`
9.富客户端应用处理耗时操作的一种办法
例子:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Work();
}
void Work()
{
Thread.Sleep(5000);
this.textBox.Text = "The answer";
}
}
当我们点击开始的时候,主线程调用work()方法,并且睡五秒,此时,UI线程处于假死状态,什么也做不了。只能等5秒后,work方法工作结束ui线程才开继续工作。
另外子线程不能修改主线程UI上的元素值 例子:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
new Thread(Work).Start();
}
void Work()
{
Thread.Sleep(5000);
this.textBox.Text = "The answer";
}
}
点击开始后将会报错。报错意思就是其他线程不能更新主线程上的UI操作。
解决办法:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
new Thread(Work).Start();
}
void Work()
{
Thread.Sleep(5000);
UpdateMessage( "The answer");
}
void UpdateMessage(string message)
{
Action action = () => textBox.Text = message;
Dispatcher.BeginInvoke(action);
}
}
BeginInvoke这个方法相当于把这个action委托排队发送到UI线程的消息队列里,而UI线程里的消息队列,就是处理一些键盘鼠标事件以及定时事件的消息队列,而委托里面就是更新UI的逻辑,所以这样写可以正常更新UI。
10.Synchronization Context(同步上下文)
Marshaling的意思:举个栗子:我们经常要把数据从一个地方移动到另一个地方,比如从一个网站移动到另一个网站,A网站可能C#编写的,里面的数据可能都是C#类表示的,B网站是GO语言编写的他里面的数据可能就是struct表示的,所以说我们需要把C#里面编写的数据移动到GO语言里面,而这2个网站之间没有共享的内存,那么怎么把C#数据变为GO的数据呢,就需要转换一下,转换为可发送的格式,这个过程就叫做Marshaling而B网站要装换为C#这个过程就叫UNMarshaling百度翻译为:解编 其实就是序列化和反序列化。
而Thread Marshaling意思:他就是把一些数据的所有权,从一个线程交给另外一个线程。
例子:
public partial class MainWindow : Window
{
SynchronizationContext _uiSyncContext;
public MainWindow()
{
InitializeComponent();
//为当前UI线程捕获Synchronization Context
_uiSyncContext = SynchronizationContext.Current;
new Thread(Work).Start();
}
void Work()
{
Thread.Sleep(5000);
UpdateMessage( "The answer");
}
void UpdateMessage(string message)
{
//把委托Marshal给UI线程
_uiSyncContext.Post(_=>textBox.Text=message,null);
//调用post相当于调用Dispather或者control上面的BeginInvoke方法
}
}
这和第九节解决方法一样,都可以解决子线程修改UI线程假死的状态。
11.线程池
短期操作可能意思就是运行时间还没线程启动时间长,,
12.开始一个Task
例子:
static void Main(string[] args)
{
Task.Run(() => { Console.WriteLine("Foo"); });
}
上面那行代码执行后什么也不会打印,因为Task默认使用线程池,也就是后台线程,前面我们说过,当主线程里面的线程都设为后台线程后,当主线程跑完后,就会停止应用程序,不管后台线程任务有没有完成。所以RUN里面的委托已经被设置为后台线程了,等后台线程反应过来时候,主线程已经执行完了,所以就什么也不会打印。
可以加一句如下代码:
这样就达到了阻塞线程的状态,控制台就会打印Foo;
这里的热任务(hot task)意思就是相当于创建完,就立即执行。
例子:
static void Main(string[] args)
{
Task task = Task.Run(()=> {
Thread.Sleep(3000);
WriteLine("Foo");
});
WriteLine(task.IsCompleted); //false
task.Wait(); //阻塞直至task完成操作
WriteLine(task.IsCompleted); //True
ReadLine();
}
结果:
其中IsCompleted表示查看当前任务是否完成!
例子:
tasks表示的是多个任务。
13.Task的返回值
例子:
class TaskDome
{
public static void M()
{
Task<int> task = Task.Run(()=> {
Console.WriteLine("Foo");
return 3;
});
int result = task.Result; //如果task没完成,那么就阻塞
Console.WriteLine(result); //3
}
}
这里有些人不知道为什么Lambda表达式里面为什么可以返回3,其实我们可以F12查看,会发现里面的委托是一个Func的委托,也就是无参,有一个T返回值的委托。
不懂Lambda演变的看这里。
再看一个例子:
public static void M1()
{
Task<int> PrintNumberTask = Task.Run(() =>
Enumerable.Range(2, 3000000).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0))
//这句话意思是生成2-300000的整数。然后用Count统计一下这些数字符合Count条件的数,条件的意思就是在原有数目的基础上,再生成2-300000平方根的数-1,然后用2-3000000里的每个数去除2-300000平方根的每个数,如果余数大于0 count就+1;
);
Console.WriteLine("Task running....");
Console.WriteLine("The answer is "+PrintNumberTask.Result);//会等待线程池里面的委托方法执行完成,阻塞当前线程,直到拿到结果。
}
14.Task的异常
例子:
Task task = Task.Run(()=> { throw null; });
try
{
task.Wait(); //task抛出的异常在这接受
}
catch (AggregateException aex)
{
//判断是否为null异常
if (aex.InnerException is NullReferenceException)
{
Console.WriteLine("Null");
}
else
{
throw;
}
结果打印Null
15.Continuation
例子:
Task<int> PrimeNumberTask = Task.Run(()=> Enumerable.Range(2,3000000).Count(n=>Enumerable.Range(2,(int)Math.Sqrt(n)-1).All(i=>n%i>0))); //列出所有的质数
var awaiter = PrimeNumberTask.GetAwaiter();//GetAwaiter获取用于等待此 Task<TResult> 的 awaiter。
//OnCompleted将操作设置为当 TaskAwaiter<TResult> 对象停止等待异步任务完成时执行。 也就是PrimeNumberTask 执行完成后才会执行OnCompleted
awaiter.OnCompleted(() =>
{
int result = awaiter.GetResult();//结束异步任务完成的等待。获得已完成任务的结果。
Console.WriteLine(result); //writes result
});
例子:
Task<int> PrimeNumberTask = Task.Run(()=> Enumerable.Range(2,3000000).Count(n=>Enumerable.Range(2,(int)Math.Sqrt(n)-1).All(i=>n%i>0))); //列出所有的质数
var awaiter = PrimeNumberTask.ConfigureAwait(false).GetAwaiter(); //GetAwaiter获取用于等待此 Task<TResult> 的 awaiter。
//OnCompleted将操作设置为当 TaskAwaiter<TResult> 对象停止等待异步任务完成时执行。
awaiter.OnCompleted(() =>
{
int result = awaiter.GetResult();//结束异步任务完成的等待。获得已完成任务的结果。
Console.WriteLine(result); //writes result
});
加了.ConfigureAwait(false)后就不会把返回的结果更新到UI线程上。
例子:
Task<int> PrimeNumberTask = Task.Run(() => Enumerable.Range(2, 3000000).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0))); //列出所有的质数
var awaiter = PrimeNumberTask.ConfigureAwait(false).GetAwaiter(); //GetAwaiter获取用于等待此 Task<TResult> 的 awaiter。
PrimeNumberTask.ContinueWith(task=> {
int result = task.Result;
Console.WriteLine(result);
});
ContinueWith和OnCompleted是一样的作用,都是task结束后的回调,或者说延迟执行。
16.TaskCompletionSource
例子:
var tcs = new TaskCompletionSource<int>();
new Thread(() =>
{
Thread.Sleep(5000);
tcs.SetResult(42);
})
{
IsBackground = true
}.Start();
Task<int> task = tcs.Task;
Console.WriteLine(task.Result); //结果 42
例子二:
public static void M1()
{
Task<int> task = Run(()=> {
Thread.Sleep(5000);
return 42;
});
Console.WriteLine(task.Result);
}
static Task<TResult> Run<TResult>(Func<TResult> function)
{
var tcs = new TaskCompletionSource<TResult>();
new Thread(() =>
{
try
{
tcs.SetResult(function());
}
catch (System.Exception ex)
{
tcs.SetException(ex);
}
}).Start();
return tcs.Task;
}
这2个例子结果都是42第二个例子只是自定义了一下Run方法。例子二解释如下图:
public static void M3()
{
var awaiter = GetAnswerTolife().GetAwaiter();//等待Task执行完成 获取用于等待此 Task<TResult> 的 awaiter。
awaiter.OnCompleted(()=> {
//OnCompleted task执行完成结束后的回调 延时执行
Console.WriteLine(awaiter.GetResult()); //GetResult获取task执行结果
});
}
static Task<int> GetAnswerTolife()
{
var tcs = new TaskCompletionSource<int>();
//实例化一个计时器
var timer = new System.Timers.Timer(5000) {
AutoReset = false //自动重置 false表示只触发一次
};
//达到指定时间间隔后触发Elapsed事件执行后面的委托
timer.Elapsed += delegate
{
timer.Dispose();//释放资源
tcs.SetResult(42);//给Task写入结果。
};
timer.Start(); //开始执行计时器
return tcs.Task;
}
对上面方法进行封装:
public static void M4()
{
//5秒之后 continuatuon 开始的时候,才占用线程 因为之前都是后台线程,OnCompleted后进行了阻塞
Delay(5000).GetAwaiter().OnCompleted(()=> {
Console.WriteLine(42);
});
}
//注意: 没有非泛型版本的TaskCompletionSource
static Task Delay(int milliseconds)
{
var tcs = new TaskCompletionSource<object>();
var timer = new System.Timers.Timer(milliseconds)
{
AutoReset = false
};
timer.Elapsed += delegate {
timer.Dispose();
tcs.SetResult(null);
};
timer.Start();
return tcs.Task;
}
看下C#自带的
例子:
17.同步和异步
这个调用图的意思就是一个函数接着一个函数调用。
18.异步和 continuation 以及语言的支持
先看同步执行的结果:
DisplaPrmeCounts();
static void DisplaPrmeCounts()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(GetPrimesCount(i * 1000000 + 2, 1000000) + "primes between" + (i * 1000000) + "and" + ((i + 1) * 1000000 - 1));
Console.WriteLine("Done!");
}
static int GetPrimesCount(int start, int count)
{
return ParallelEnumerable.Range(start, count).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0));
}
}
结果:
同步执行,我们可以看到,数据都是从大到小排列。因为都是一个线程排列同步执行的。
现在看异步执行。并行执行的例子:
static void DisplaPrmeCounts()
{
for (int i = 0; i < 10; i++)
{
var awaiter = GetPrimesCountAsync(i * 1000000 + 2, 1000000).GetAwaiter();
awaiter.OnCompleted(()=>
Console.WriteLine(awaiter.GetResult()+"primes betwee...")
);
}
}
static Task<int> GetPrimesCountAsync(int start, int count)
{
return Task.Run(()=> ParallelEnumerable.Range(start, count).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
}
结果:
结果看上去很乱,这是因为线程池中的线程相当于分配了10个线程给他们,然后CUP的时间片相当于同步并行的执行了这些方法。这种并行执行效率高了,但是顺序很乱,如何让它顺序不乱了 看下面
调整后 的代码:
DisplayPrimeCountsFrom(0);
static void DisplayPrimeCountsFrom(int i)
{
var awaiter = GetPrimesCountAsync(i * 1000000 + 2, 1000000).GetAwaiter();
awaiter.OnCompleted(()=> {
Console.WriteLine(awaiter.GetResult()+"primes between ...");
if (++i < 10)
{
DisplayPrimeCountsFrom(i);
}
else
{
Console.WriteLine("Done!");
}
});
}
static Task<int> GetPrimesCountAsync(int start, int count)
{
return Task.Run(()=> ParallelEnumerable.Range(start, count).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
}
这样改了后其实并不是异步的,而是同步的,因为采用的递归方式,如何改成异步的呢,看下面代码:
public static async void M()
{
await DisplaPrmeCountsAsync();
}
async static Task DisplaPrmeCountsAsync()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(await GetPrimesCountAsync(i * 1000000 + 2, 1000000) + "primes between" + (i * 1000000) + "and" + ((i + 1) * 1000000 - 1));
Console.WriteLine("Done!");
}
public static Task<int> GetPrimesCountAsync(int start, int count)
{
return Task.Run(()=> ParallelEnumerable.Range(start, count).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
}
19.await
例子:
await 接受的返回值必须是Task 或者Task不能接void需要await的函数体里面必须加上async关键字,不然编译时候会报错。
这2种写法是等效的。但第二种简介了不少,可以说第二种是第一种的简写吧。第二种写法,当运行时走到await表达式后,它会加一个continuation等待执行结果,又因为是异步函数,所以主线程也不存在卡死的情况,当await后面任务完成后,运行时会回到之前停止的continuation,然后完成打印操作。
比如循环体里的i 每次循环到await这里后 他都会对每次的i进行捕获。
例子:
同步:
这种同步的执行后,由于UI线程需要进行计算,所以会有个假死的状态。
改成异步:
下载网络内容的例子:
这个代码的意思 就是当你点击下载的时候, 遇到await后,代码会和同步执行一样,接着走后面的代码,比如把foreach运行完,只不过await后面的表达式内容,长时间执行的任务,相当于被暂停了,编译器会自己给它那里加一个continuation等表达式里面的任务运行完后,再返回到停止的地方,把没执行完的代码执行完,这个没执行完的代码就好比向UI界面输入返回的结果。
粗粒度并发的例子:
20.编写异步函数
例子:
返回值为Viod的话,调用函数不能使用await关键字,只有Task才可以使用。
比如上面方法体并没有return关键字。
异步方法之间的调用就是调用链。
编译器进行扩展的代码大致如下:
例子:
例子:
如果不加await 则返回的函数值就是Task 加了之后 就是int类型返回值。
可以和同步代码进行比较:
基本上差不多,只不过异步加了async/await;
需要自己手动创建Task的情况:
21.异步中的同步上下文
22.优化同步完成
下面这种不会引发警告。
23.ValueTask