C#异步编程基础入门总结

Posted web全栈开发极客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#异步编程基础入门总结相关的知识,希望对你有一定的参考价值。

1.前言

异步这概念刚开始接触的时候,不是那么容易接受,但是需要用的地方还真的挺多的,刚学习的时候,也很懵逼走了不少弯路,所以这里有必要总结一下。 
msdn文档:https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/ 
官方的简介: 
*.NET Framework提供了执行异步操作的三种模式: 
异步编程模型(APM)模式(也称为IAsyncResult的模式),其中异步操作要求Begin和End方法(例如,BeginWrite和EndWrite异步写入操作)。这种模式不再被推荐用于新开发。有关更多信息,请参阅异步编程模型(APM)。

基于事件的异步模式(EAP),它需要一个具有Async后缀的方法,并且还需要一个或多个事件,事件处理程序委托类型和被EventArg派生类型。EAP在.NET Framework 2.0中引入。不再推荐新的开发。有关更多信息,请参阅基于事件的异步模式(EAP)。

基于任务的异步模式(TAP),它使用单一方法来表示异步操作的启动和完成。TAP在.NET Framework 4中引入,是.NET Framework中推荐的异步编程方法。C#中的async和等待关键字,Visual BasiC语言中的Async和Await运算符为TAP添加语言支持。有关更多信息,请参阅基于任务的异步模式(TAP)。*

2.异步的应用场景

在计算机程序的运行中,计算是需要一定的时间的,在运算时间过长的任务时,比如上传大文件,客户端请求数据,读取文件流等,如果是同步(synvronous)必须等待该任务执行完成才能继续下一个任务。使用异步(asynchronous)操作,会开启新的线程,不会等待异步操作完成才去执行后面的程序,相比异步编程优点:1.就是出现长时间处理程序时,不会卡界面,用户仍然可以操作UI界面2.提高程序运行效率,节约CPU资源,提供系统吞吐量。

3.进程和线程的关系

4.异步和多线程的区别

异步是相对同步而言的,我们知道异步是开启了新线程,但是和多线程不是一个概念,异步相当于一个人的“大脑”能够做试卷,又能够看电影,同时处理两件以上不同的事情。多线程好比多个人做不同的事情。

5.C#异步方式之一( BeginInvoke、EndInvoke方法)

方式1:使用回调方法完成异步委托 
先来看个例子,委托的异步调用,这个例子首先定义一个string类型的返回值、string类型的参数的委托。虽然这中模式不推荐被使用。

    class Program
    {        
         delegate string SayHi(string name);//定义委托        static void Main(string[] args)        {            SayHi sayhi = new SayHi(SayHiName);//实例化委托            sayhi("科比");//一般的直接调用            sayhi.Invoke("张林");//使用Invoke方法同步调用            //异步调用            sayhi.BeginInvoke("杜兰特", (IAsyncResult ar) =>            {                sayhi.EndInvoke(ar);                Console.WriteLine("打招呼成功结束");            }, null);        }
         public static string SayHiName(string  name)        {
            return "how are you"+name + "?";        }    }

前两种调用委托的方式都是同步的,BeginInvoke方法的返回值是IAsyncResult类型的 
该方法的参数由两部分组成,前面(n)个参数是委托的参数,倒数第二个参数也表示一个委托,该委托是.net系统定义的委托(和func、action类似),查看AsyncCallback的定义如图: 
C#异步编程基础入门总结 
作用就是:作为执行调用的回调方法,值得注意的是,在回调方法中,必须调用EndInvoke方法结束异步调用,EndInvoke是获取异步调用的结果 
上面的例子调试的结果如图: 
C#异步编程基础入门总结

方式2:使用轮询 
我们把BeginInvoke的委托参数为null,使用轮询的方式

   Func<string, string> func = delegate (string name)
              {
                  Thread.Sleep(2000);                
                      return "how are you" + name + "";              };              IAsyncResult ar = func.BeginInvoke("张林",null,null);              
                    int i = 1;            
                  while (!ar.IsCompleted)              {                Console.WriteLine(200*i);                i++;                Thread.Sleep(200);              }            
               string result = func.EndInvoke(ar);             Console.WriteLine(result);

结果如图: 
C#异步编程基础入门总结

6.C#异步方式之二 await async

async和await是一对关键字,它是.net 4.5的特性。在实际工作中使用方便灵活,主要原因就是可以像写同步方法那样去异步编程,代码结构清晰,不用关心如何实现异步的编程。 
这里其实要注意的是,之前刚说了异步是开启新的线程来实现的,但是await 和async两个关键字并没有开启新的线程,为了证明这一点,下面建了一个winform的程序,异步获取图片并显示到picturebox上。

          public Form1()
        {
            InitializeComponent();           
               this.label1.Text = "主线程Id:"+Thread.CurrentThread.ManagedThreadId;        }        
          private  async void button1_Click(object sender, EventArgs e)        {  
                string imageUrl = "https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=3850265187,1181041963&fm=173&s=62E19A4722716A371EB097FB03009015&w=218&h=146&img.JPEG";            HttpClient client = new HttpClient();          
                 var response =await  client.GetAsync(imageUrl);            
                 if (response.StatusCode == System.Net.HttpStatusCode.OK)            {  
                     var stream =await response.Content.ReadAsStreamAsync();                Image  image = Bitmap.FromStream(stream,true);                
                     this.label2.Text = "线程Id:" + Thread.CurrentThread.ManagedThreadId;              
                     this.pictureBox1.BackgroundImage = image;            }        }

其实不用看图就已经知道答案了,程序运行时不报异常,就已经说明一点:await async两个关键根本创建新的线程。这个涉及到异步更新UI到主线程,就不多说了。 
结果如图:

C#异步编程基础入门总结 
async await方法的使用说明:

  • 返回类型: void 、Task、Task<泛型类型>

  • async、await不会创建新的线程,实现等待的效果,必须同时使用

  • 使用该方法的方法主体也要用async关键字

异步方法事例:

     private static async Task<int> GetValueAsync(int a)
        {            
              //Task.run 开启了新的线程            await  Task.Run(() =>            {                Thread.Sleep(2000); //模拟耗时                Console.WriteLine("GetValueAsync方法结束,线程ID:" + Thread.CurrentThread.ManagedThreadId);                
                     a=a * a;            });            Console.WriteLine("线程ID:" + Thread.CurrentThread.ManagedThreadId+"异步方法结束");            
              return a;        }

调用异步方法:

  private async void button1_Click(object sender, EventArgs e)
        {           
            int result =await GetValueAsync(5);        
           this.label1.Text = "异步计算的结果" + result + "线程ID:" + Thread.CurrentThread.ManagedThreadId;        }

7.C#异步方式之三 浅谈Task

前面刚刚了解到async await是.net 4.5出的特性,Task是.net4.0新出的特性,用来处理异步编程的,其实我们要知道真正实现的异步操作还是Task新增线程来实现的,但是不代表说开一个Task,就开一个线程,有可能是几个Task在一个线程上运行的,他们并不是一一对应的关系,充分利用线程,下面的事例就已经能够说明这一点 
Task创建 
Task创建有两种方式一种通过任务工厂赋值立即运行,一种是直接实例化。下面这个例子创建了10个Task

     static  void Main(string[] args)
        {
            //启用线程池中的线程异步执行
            Task t1 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task1启动...线程ID:"+Thread.CurrentThread.ManagedThreadId);
            });
            Task t2 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task2启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            //new 实例化启动
            Task t3 = new Task(() =>
            {
                Console.WriteLine("Task3启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            t3.Start();
            Task t4 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task4启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            Task t5 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task5启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            Task t6 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task6启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            Task t7 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task7启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            Task t8 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task8启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            Task t9 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task9启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            Task t10 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task10启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            Console.ReadLine();
        }


创建的10个Task,我们从结果中也证明了Task和线程并不是一一对应的关系,结果如图: 
C#异步编程基础入门总结

Task构造函数

C#异步编程基础入门总结

Task状态 
我们创建一个task,调用他的Start、Wait方法

 static  void Main(string[] args)
        {            
               var task = new Task(()=> {                Console.WriteLine("Task创建成功");            });            
                 Console.WriteLine("task未开始:"+task.Status);      
              task.Start();          
                Console.WriteLine("task已经开始:"+task.Status);            
                 task.Wait();            
                 Console.WriteLine("task已经等待:"+task.Status);        }

结果如图: 
C#异步编程基础入门总结 
我们从图中可以知道,Task的生命周期如下: 
Created:在已经实例化未Start之前的状态 
WaittingToRun:表示等待分配线程给Task执行 
RanToCompletion:任务执行完毕

Task等待任务结果 
1.Task.WaitAll从这个字面意思就知道等待所有任务执行完成,和上面例子Wait方法等待一个任务执行完成很相似,我们来看一个代码:

 var task1 = new Task(() =>            {                System.Threading.Thread.Sleep(3000);                Console.WriteLine("task1Created");            });            var task2 = new Task(() =>            {                System.Threading.Thread.Sleep(3000);                Console.WriteLine("task2Created");            });           
                task1.Start();            
                task2.Start();            
                Task.WaitAll(task1, task2);            
                Console.WriteLine("所有任务执行完!");            
                Console.Read();

结果输出: 
task1Created 
task2Created 
所有任务执行完 
除了WaitAll方法还有这些常用的方法

  • Task.WaitAny:等待任何一个任务向下执行

  • Task.ContinueWith等待第一个Task完成自动启动,触发下一个Task,也就是当做任务完成时触发的回调方法

  • Task.GetAwaiter().OnCompleted(Action action) :GetAwaiter 方法获取任务的等待者,调用OnCompleted事件,任务完成时触发

Task任务取消

   static  void Main(string[] args)
        {     
               var source = new CancellationTokenSource();  
               var token = source.Token;            Task t1 = Task.Run(() =>            {                Thread.Sleep(2000);
                       if (token.IsCancellationRequested)                {                    Console.WriteLine("任务已取消");                }                Thread.Sleep(1000);            },token)
;
                Console.WriteLine(t1.Status);            //取消任务
                source.Cancel();
                Console.WriteLine(t1.Status);        
               Console.ReadLine();        }

结果如图: 


以上是关于C#异步编程基础入门总结的主要内容,如果未能解决你的问题,请参考以下文章

关于C#异步编程你应该了解的几点建议

异步编程你必须知道的6个最佳实践

C#基础(二十):异步编程

C#入门基础语法知识点总结(.NET开发环境及代码编写规范)

C#异步编程一

[C#] 异步编程 - 剖析异步方法