异步编程:
同步:按照顺序,一件事情做完了才能接着做一件事情,比如主函数中执行多个顺序方法和语句,只有上一条语句或者方法执行完毕才能执行下一个方法或者语句。
异步:开始一个任务(比如一个方法)后,让任务在另一个线程中执行,本线程可以继续执行别的事情,然后等待那个任务执行完毕
比如一个UI按钮,UI就相当于一个主函数,而按钮事件就是调用的方法,如果使用同步,那么在方法执行期间,整个ui界面就会发生阻塞,从而导致无法做任何操作
多线程和异步不是同一个概念,多线程是用于实现异步的一种方式。
异步可以用线程来实现也可以不用线程实现,C#使用线程来实现
传统方法一,异步模式,使用委托的BeginInvoke及EndInvoke
class Program { public delegate int FooDelegate(string s); static void Main(string[] args) { Console.WriteLine("主线程" + Thread.CurrentThread.ManagedThreadId); FooDelegate fooDelegate = Foo; IAsyncResult result = fooDelegate.BeginInvoke("Hello World", null, null); Console.WriteLine("主线程继续执行做其他事..."); int n = fooDelegate.EndInvoke(result); Console.WriteLine("回到主线程" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("结果是" + n); Console.ReadKey(true); } public static int Foo(string s) { Console.WriteLine("函数所在线程" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("异步线程开始执行:" + s); Thread.Sleep(1000); return s.Length; } }
传统方法二,基于事件,对于完成后打印结果等后续语句,使用委托回调
class Program_Callback { public delegate int FooDelegate(string s); static void Main(string[] args) { Console.WriteLine("主线程" + Thread.CurrentThread.ManagedThreadId); FooDelegate fooDelegate = Print; IAsyncResult result = fooDelegate.BeginInvoke("Hello World", PrintComplete, fooDelegate);//这里回调PrintComplete方法 Console.WriteLine("主线程继续执行做其他事..."); Console.ReadKey(true); } public static int Print(string s) { Console.WriteLine("函数所在线程" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("异步线程开始执行:" + s); Thread.Sleep(1000); return s.Length; } public static void PrintComplete(IAsyncResult result) { (result.AsyncState as FooDelegate).EndInvoke(result); Console.WriteLine("当前线程结束" + result.AsyncState.ToString()); } }
最新方式,基于任务,使用await和async关键字和Task类型
await 表示等待任务执行,用来调用返回任务的方法,或者阻塞等待一个任务执行完毕
async修饰一个方法,表示其中有await语句
await task相当于EndInvoke()用于阻塞等待任务线程执行完毕
class Program_awaitAndAsync { static async Task Main(string[] args) { Task<double> task = FacAsync(10); Console.WriteLine("主函数继续执行。。。"); for (int i = 0; i < 10; i++) Console.WriteLine("main"); double result = await task;//此处await对程序进行阻塞,直到task执行完毕 才会执行接下来的语句 Console.WriteLine("结果为"+result); Console.ReadKey(true); } public static Task<double> FacAsync(int n) { //Run会开启一个新的线程来执行委托的程序 return Task<double>.Run(() => { double s = 1; for (int i = 1; i < n; i++) { s = s + i; Console.WriteLine("task"); } return s; }); } async void Test() { double result = await FacAsync(10); Console.WriteLine(result); } }
任务与并行编程:
并行任务库TPL(Task Parallel Library)
最重要的是Task类和Parallel类。
Task类,是利用线程池来进行任务的执行,比直接用ThreadPool更优化,更方便
Parallel类,是 并行执行任务类的实用类,好处是可以隐式地包装使用Task,更方便
多任务与多线程的关系,可以粗略的认为多任务的目标是充分使用多核cpu,而多线程却不一定,可能多线程使用的是同一个cpu,并且多任务的底层实现上使用到了多线程。
使用Task.Run方法来得到Task实例
Task<double> task=Task.Run(()=>SomeFun());
double result=task.Result;//等待直到获得结果
可以使用Task.WaitAll(task数组)等待所有task执行完毕
可以使用task.ContinueWith(另一个task)来使得task一个一个执行
class Program { static void Main(string[] args) { Task<double>[] tasks = { Task.Run(()=>SomeFun()), Task.Run(() => SomeFun()) }; Thread.Sleep(1); for(int i = 0; i < tasks.Length; i++) { Console.WriteLine(tasks[i].Status);//状态 Console.WriteLine(tasks[i].Result);//获取结果,这里相当于wait,会等到任务结束 } //Task.WaitAll(tasks);//等待所有任务结束 } static double SomeFun() { return 1; } }
Parallel类的使用:
Parallel.Invoke(Action[] actions);并行执行多个任务,直到完成
Parallel.For(0,100,i=>{...})
Parallel.Foreach(list,Item=>{...})
示例:并行计算矩阵乘法
class Program_Parallel { //一般的矩阵相乘 static void MultiMatrixNormal(double[,] matA, double[,] matB, double[,] result) { int m = matA.GetLength(0); int n = matA.GetLength(1); int t = matB.GetLength(1); for(int i = 0; i < m; i++) { for(int j = 0; j < t; j++) { double temp = 0; for(int k = 0; k < n; k++) { temp += matA[i, k] * matB[k, j]; } result[i, j] = temp; } } } //使用了多任务的矩阵相乘 static void MultiMatrixParallel(double[,] matA, double[,] matB, double[,] result) { int m = matA.GetLength(0); int n = matA.GetLength(1); int t = matB.GetLength(1); Parallel.For(0, m, i => { { for (int j = 0; j < t; j++) { double temp = 0; for (int k = 0; k < n; k++) { temp += matA[i, k] * matB[k, j]; } result[i, j] = temp; } } }); } }
并行Linq(即PLinq)
只要在集合上加上个.AsParallel()
var a=(from n in persons.AsParallel() where n.Age>20&&n.Age<25 select n).ToList();
下面是摘抄自https://blog.csdn.net/birdfly2015/article/details/105024216
一、区别与联系
从技术发展的时间线上,微软推出这几个的顺序是:Thread=>ThreadPool=>Task=>Parallel
Thread
优点在于提供了丰富的多线程操作API,缺点在于线程个数的使用不加限制,以及无法复用线程,因此推出了TheadPool技术
ThreadPool
优点在于解决了线程个数的限制以及线程的复用,缺点在于API较少而且线程等待问题解决起来较为麻烦,因此推出了Task技术
Task
优点在于丰富的API,以及多线程的方便管理,缺点在于线程数量控制较为麻烦,因此推出了Parrel技术
Parallel
优点在于丰富的API,多线程方便管理,线程数量控制简单,但是主线程也会参与计算,导致主线程的阻塞问题(但是这个问题可以通过包一层来解决)
二、最佳实践
1.通常绝大部分问题通过Task就能很好的解决
2.最好不要使用线程嵌套线程
3.注意访问公共资源需要加锁
4.多线程参数的传递
5.多线程完成时的判断