开篇
异步编程是程序设计的重点也是难点,还记得在刚开始接触.net的时候,看的是一本c#的Winform实例教程,上面大部分都是教我们如何使用Winform的控件以及操作数据库的实例,那时候做的基本都是数据库的demo,数据量也不大,程序在执行的时候基本上不会出现阻塞的情况。随着不断的深入.net,也开始进入的实战,在实际的项目,数据量往往都是比较大,特别是在大量的数据入库以及查询数据并进行计算的时候,程序的UI界面往往卡死在那里,发生了阻塞,这时候就需要对计算时间限制的过程进行异步处理,让UI线程继续相应用户的操作,使得用户体验表现比较友好,同时正确的使用异步编程去处理计算限制的操作和耗时IO操作还能提升的应用程序的吞吐量及性能。由此可见,异步编程的重要性。
class Program { public delegate void DoWork(); static void Main(string[] args) { DoWork d = new DoWork(WorkPro);//no.1 d.BeginInvoke(null, null);//no.2 for (int i = 0; i < 100; i++)//no.3 { Thread.Sleep(10);//主线程需要做的事 } Console.WriteLine("主线程done"); Console.ReadKey(); } public static void WorkPro() { //做一些耗时的工作 Thread.Sleep(2000); Console.WriteLine("异步调用结束"); } }
![](https://i-blog.csdnimg.cn/blog_migrate/d8bbb8fec3c4e6dc0f8af76e4db0d2af.png)
class Program { public delegate int DoWord(int count); static void Main(string[] args) { DoWord d = new DoWord(WorkPro); IAsyncResult r= d.BeginInvoke(1000,null,null);//no.1 int result= d.EndInvoke(r);//no.2 Console.WriteLine(result); for (int i = 0; i < 100; i++)//no.3 { Thread.Sleep(10);//主线程需要做的事 } Console.WriteLine("主线程done"); Console.ReadKey(); } public static int WorkPro(int count) { int sum = 0; //做一些耗时的工作 for (int i = 0; i < count; i++) { sum += i; } return sum; } }
class Program { public delegate int DoWord(int count); static void Main(string[] args) { DoWord d = new DoWord(WorkPro); IAsyncResult r= d.BeginInvoke(100,CallBack ,d);//no.1 for (int i = 0; i < 100; i++) { Thread.Sleep(10);//主线程需要做的事 } Console.WriteLine("主线程done"); Console.ReadKey(); } public static int WorkPro(int count) { int sum = 0; //做一些耗时的工作 for (int i = 0; i < count; i++) { sum += i; Thread.Sleep(10); } return sum; } public static void CallBack(IAsyncResult r) { DoWord d = (DoWord)r.AsyncState; Console.WriteLine("异步调用完成,返回结果为{0}", d.EndInvoke(r)); } }
.net在System.Threading和System.Threading.Tasks这两个命名空间中提供了Thread,ThreadPool,和Task三个类来处理多线程的问题,其中Thread是建立一个专用线程,ThreadPool是使用线程池中工作线程,而Task类是采用任务的方式,其内部也是使用线程池中的工作线程。本节只讲Tread类和Tasks类的使用以及其优劣。
class Program { static void Main(string[] args) { Thread t = new Thread(WorkPro);//no.1 t.IsBackground = true;//no.2 t.Start(1000);//no.3 } public static void WorkPro(object t) { //做一些耗时的工作 int count=(int)t; for (int i = 0; i < count; i++) { Thread.Sleep(2000); } Console.WriteLine("任务处理完成"); } }
2、Task类
class Program { static void Main(string[] args) { Task t = new Task((c) => { int count = (int)c; for (int i = 0; i < count; i++) { Thread.Sleep(10); } Console.WriteLine("任务处理完成"); }, 100);//no.1 t.Start(); for (int i = 0; i < 100; i++) { Thread.Sleep(10); } Console.WriteLine("done"); } }
class Program { static void Main(string[] args) { Task<int> t = new Task<int>((c) => { int count = (int)c; int sum=0; for (int i = 0; i < count; i++) { Thread.Sleep(10); sum+=i; } Console.WriteLine("任务处理完成"); return sum; }, 100); t.Start(); t.Wait();//no.1 Console.WriteLine("任务执行的结果{0}", t.Result);//no.2 for (int i = 0; i < 100; i++) { Thread.Sleep(10); } Console.WriteLine("done"); } }
class Program { static void Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource();//no.1 Task<int> t = new Task<int>((c) =>Sum(cts.Token ,(int)c), 100);//no.2 t.Start(); cts.Cancel();//no.3如果任务还没完成,但是Task有可能完成啦 for (int i = 0; i < 100; i++) { Thread.Sleep(10); } Console.WriteLine("done"); } static int Sum(CancellationToken ct, int count) { int sum = 0; for (int i = 0; i < count; i++) { if (!ct.CanBeCanceled) { Thread.Sleep(10); sum += i; } else { Console.WriteLine("任务取消"); //进行回滚操作 return -1;//退出任务 } } Console.WriteLine("任务处理完成"); return sum; } }
public Task ContinueWith( Action<Task> continuationAction, TaskContinuationOptions continuationOptions )第二个参数代表新任务的执行条件,当任务满足这个枚举条件才执行 Action<Task>类型的回调函数。
class Program { static void Main(string[] args) { Task<int> t = new Task<int>((c) =>Sum((int)c), 100); t.Start(); t.ContinueWith(task => Console.WriteLine("任务完成的结果{0}", task.Result));//当任务执行完之后执行 t.ContinueWith(task => Console.WriteLine(""), TaskContinuationOptions.OnlyOnFaulted);//当任务出现异常时才执行 for (int i = 0; i < 200; i++) { Thread.Sleep(10); } Console.WriteLine("done"); } static int Sum( int count) { int sum = 0; for (int i = 0; i < count; i++) { Thread.Sleep(10); sum += i; } Console.WriteLine("任务处理完成"); return sum; } }
t.Start()之后调用第一个ContinueWith方法,该方法第一参数就是一个Action<Task>的委托类型,相当于是一个回调函数,在这里我也用lambda表达式,当任务完成就会启用一个新任务去执行这个回调函数。而第二个ContinueWith里面的回调方法却不会执行,因为我们的任务也就是Sum方法不会发生异常,不能满足TaskContinuationOptions.OnlyOnFaulted这个枚举条件。这种用法比委托的异步函数编程看起来要简单些。最关键的是ContinueWith的还有一个重载版本可以带一个TaskScheduler对象参数,该对象负责执行被调度的任务。FCL中提供两种任务调度器,均派生自TaskScheduler类型:线程池调度器,和同步上下文任务调用器。而在Winform窗体程序设计中TaskScheduler尤为有用,为什么这么说呢?因为在窗体程序中的控件都是有ui线程去创建,而我们所执行的后台任务使用线程都是线程池中的工作线程,所以当我们的任务完成之后需要反馈到Winform控件上,但是控件创建的线程和任务执行的线程不是同一个线程,如果在任务线程中去更新控件就会导致控件对象安全问题会出现异常。所以操作控件,就必须要使用ui线程去操作。因此在ContinueWith获取任务执行的结果的并反馈到控件的任务调度上不能使用线程池任务调用器,而要使用同步上下文任务调度器去调度,即采用ui这个线程去调用ContinueWith方法所绑定的回调用函数即Action<Task>类型的委托。下面将使用任务调度器来把异步执行的Sum计算结果反馈到Winform界面的TextBox控件中。
界面如下。
代码如下。
public partial class Form1 : Form { private readonly TaskScheduler contextTaskScheduler;//声明一个任务调度器 public Form1() { InitializeComponent(); contextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();//no.1获得一个上下文任务调度器 } private void button1_Click(object sender, EventArgs e) { Task<int> t = new Task<int>((n) => Sum((int)n),100); t.Start(); t.ContinueWith(task =>this.textBox1 .Text =task.Result.ToString(),contextTaskScheduler);//当任务执行完之后执行 t.ContinueWith(task=>MessageBox .Show ("任务出现异常"),CancellationToken.None ,TaskContinuationOptions.OnlyOnFaulted,contextTaskScheduler );//当任务出现异常时才执行 } int Sum(int count) { int sum = 0; for (int i = 0; i < count; i++) { Thread.Sleep(10); sum += i; } Console.WriteLine("任务处理完成"); return sum; } }