.NET&C#异步编程

1.  演进过程

          本文档主要记录.net平台下异步编程不同时期的不同实现方案,.net平台异步编程经历了以下几次演变:

  1. Asynchronous Programming Model(APM):这种模式又被成为IAsyncResult模式,在.net1.0时提出,在同步方法中通过调用BeginXXXX和EndXXXX开头的方法对实现异步操作,此模式需要分配和回收IAsyncResult对象消耗资源降低效率,且不支持取消和没有提供进度报告的功能,微软不推荐使用。
  2. Event-based Asynchronous Pattern(EAP):它是基于事件模式的异步实现,在.net2.0时提出,这种模式具有一个或多个以Async为后缀的方法和Completed事件,它们都支持异步方法的取消、进度报告和报告结果,且其基于APM模式,此模式效率虽高,但.net中并不是所有类都支持,且业务复杂时就很难控制,微软不推荐使用。
  3. Task-based Asynchronous Pattern(TAP:task):它是基于任务模式的异步实现,在.net4.0时提出,这种模式有四种方法创建Task,1.Task.Factory.StartNew()2.(new Task(()=>{ //TODO })).Start()3.Task.Run()是.net4.5增加4.Task.FromResult(),微软推荐使用的。
  4. Task-based Asynchronous Pattern(TAP:async/await):它是基于任务模式的异步实现,在.net4.5时提出,它与第三种实现实质上相等,使用这两个关键字会使代码看起来与同步代码相当和简洁,进一步摒弃掉异步编程的复杂结构,微软极力推荐使用的异步编程模式。

2.  模式:APM和EAP

2.1.  APM

本人在WCF时期应用APM模式调用服务使用最广泛,现在除了UI交互外很少使用APM模式,以下示例仅为展示APM编码模式

public void Test(){
    var urlStr="http://www.test.com/test/testAPM";
    var request=HttpWebRequest.Create(url);
    request.BeginGetResponse(AsyncCallbackImpl,request);//发起异步请求
}
public void AsyncCallbackImpl(IAsyncResult ar){
    var request=ar.AsyncState as HttpWebRequest;
    var response=request.EndGetResponse(ar);//结束异步请求
    using(var stream=response.GetResponseStream()){
        var sbuilder=new StringBuilder();
        sbuilder.AppendLine($"当前线程Id:{Thread.CurrentThread.ManagedThreadId}");
        var reader=new StreamReader(stream);
        sbuilder.AppendLine(reader.ReadLine());
        Console.WriteLine(sbuilder.ToString());
    }
}

2.2.  EAP

在大多数数据库连接驱动中使用,本人在即时通信软件中使用过,以下示例仅为展示EAP编码模式

2.2.1.  Demo:WebClient

public void Test(){
    var wc=new WebClient();
    wc.DownloadStringCompleted+=wc_DownloadStringCompleted;
    wc.DownloadStringAsync(new Uri("http://www.test.com/test/testEAP"));
}
public void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e){
    Console.WriteLine(e.Result);
}

2.2.2.  Demo:BackgroundWorker

public void Test(){
    var bgworker=new BackgroundWorker();
    bgworker.DoWork+=bgworker_DoWork;
    bgworker.RunWorkerCompleted+=bgworker_RunWorkerCompleted;
    bgworker.RunWorkerAsync(null);//参数会被传递到DoWork事件订阅者方法中,而内部实际调用了BeginInvoke()方法
}
public void bgworker_DoWorker(object sender,DoWorkEventArgs e){
    Console.WriteLine("dowork");
}
public void bgworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){
    Console.WriteLine("dowork completed");
}

3.  模式:TAP

3.1.  常用对象和方法

由于微软推荐使用TAP方式编码,所以本节内容是本篇文章的重点。其实TAP主要使用了以下对象和方法实现异步编程:

1)      Task<Result>:异步任务

2)      Task<Result>.ContinueWith(Action):延续任务,指定任务执行完成后延续的操作

3)      Task.Run():创建异步任务

4)      Task.WhenAll():在所有传入的任务都完成时才返回Task

5)      Task.WhenAny():在传入的任务其中一个完成就会返回Task

6)      Task.Delay():异步延时等待,示例Task.Delay(2000).Wait()

7)      Task.Yield():进入异步方法后,在await之前,如果存在耗时的同步代码,且你想让这部分代码也异步执行,那么你就可以在进入异步方法之后的第一行添加await Task.Yield()代码了,因为它会强制将当前方法转为异步执行。

3.2.  关键字:async/await

1)      使用async关键字标记的方法成为异步方法,异步方法通常包含await关键字的一个或多个实例,如果异步方法中未使用await关键字标识对象方法,那么异步方法会视为同步方法。

2)      await关键字无法等待具有void返回类型的异步方法,并且void返回方法的调用方捕获不到异步方法抛出的任何异常。

3)      异步方法无法声明in、ref或out参数,但可以调用包含此类参数的方法。

3.3.  使用示例

3.3.1.  同步方法

public void Test(){
    Console.WriteLine($"头部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    var result = SayHi("abc");
    Console.WriteLine(result);
    Console.WriteLine($"尾部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    Console.ReadKey();
}
public string SayHi(string name){
    Task.Delay(2000).Wait();//异步等待2s
    Console.WriteLine($"SayHi执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    return $"Hello,{name}";
}

3.3.2.  异步实现

public void Test(){
    Console.WriteLine($"头部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    var result = SayHiAsync("abc").Result;
    Console.WriteLine(result);
    Console.WriteLine($"尾部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    Console.ReadKey();
}
public Task<string> SayHiAsync(string name){
    return Task.Run<string>(() => { return SayHi(name); });
}
public string SayHi(string name){
    Task.Delay(2000).Wait();//异步等待2s
    Console.WriteLine($"SayHi执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    return $"Hello,{name}";
}

3.3.3.  延续任务

public void Test(){
    Console.WriteLine($"头部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    var task = SayHiAsync("abc");
    task.ContinueWith(t=>{
        Console.WriteLine($"延续执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
        var result=t.Result;
        Console.WriteLine(result);
    });
    Console.WriteLine($"尾部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
        Console.ReadKey();
}
public Task<string> SayHiAsync(string name){
    return Task.Run<string>(() => { return SayHi(name); });
}
public string SayHi(string name){
    Task.Delay(2000).Wait();//异步等待2s
    Console.WriteLine($"SayHi执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    return $"Hello,{name}";
}

3.3.4.  async/await重构

public void Test(){
    Console.WriteLine($"头部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    SayHiKeyPair("abc");
    Console.WriteLine($"尾部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    Console.ReadKey();
}
public async void SayHiKeyPair(string name){
    Console.WriteLine($"异步调用头部执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    var result = await SayHiAsync(name);
    Console.WriteLine($"异步调用尾部执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    Console.WriteLine(result);
}
public Task<string> SayHiAsync(string name){
    return Task.Run<string>(() => { return SayHi(name); });
}
public string SayHi(string name){
    Task.Delay(2000).Wait();//异步等待2s
    Console.WriteLine($"SayHi执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    return $"Hello,{name}";
}

3.4.  运行流程

为了避免繁杂的概念,简单明了的概述为:XXXXAsync方法返回一个Task<Result>,await Task<Result>处等待异步结果,在它们中间可以执行一些与异步任务无关的逻辑。

4.  转为:TAP

4.1.  APM转化为TAP

现在将第二节中的APM实现转为TAP实现,主要借助Task.Factory.FromAsync方法

public void APMtoTAP(){
    var urlStr="http://www.test.com/test/testAPM";
    var request=HttpWebRequest.Create(url);
        Task.Factory.FromAsync<HttpWebResponse>(request.BeginGetResponse,request.EndGetResponse,null,TaskCreationOptions.None)
            .ContinueWith(t=>{
                var response=null;
                try{
                    response=t.Result;
                    using(var stream=response.GetResponseStream()){
                        var sbuilder=new StringBuilder();
                        sbuilder.AppendLine($"当前线程Id:{Thread.CurrentThread.ManagedThreadId}");
                        var reader=new StreamReader(stream);
                        sbuilder.AppendLine(reader.ReadLine());
                        Console.WriteLine(sbuilder.ToString());
                    }
                }catch(AggregateException ex){
                    if (ex.GetBaseException() is WebException){
                        Console.WriteLine($"异常发生,异常信息为:{ex.GetBaseException().Message}");
                    }else{
                        throw;
                    }
                }finally{
                    if(response!=null){
                        response.Close();
                    }
                }
            });
}

4.2.  EAP转化为TAP

public void Test(){
    var wc=new WebClient()// WebClient类支持基于事件的异步模式(EAP)
    var tcs = new TaskCompletionSource<string>();//创建TaskCompletionSource和它底层的Task对象

    wc.DownloadStringCompleted+=(sender,e)=>{//一个string下载好之后,WebClient对象会应发DownloadStringCompleted事件
        if(e.Error != null){
            tcs.TrySetException(e.Error);//试图将基础Tasks.Task<TResult>转换为Tasks.TaskStatus.Faulted状态
        }else if(e.Cancelled){
            tcs.TrySetCanceled();//试图将基础Tasks.Task<TResult>转换为Tasks.TaskStatus.Canceled状态
        }else{
            tcs.TrySetResult(e.Result);//试图将基础Tasks.Task<TResult>转换为TaskStatus.RanToCompletion状态。
        }
};
    tsc.Task.ContinueWith(t=>{//为了让下面的任务在GUI线程上执行,必须标记为TaskContinuationOptions.ExecuteSynchronously
        if(t.IsCanceled){
            Console.WriteLine("操作已被取消");
        }else if(t.IsFaulted){
            Console.WriteLine("异常发生,异常信息为:" + t.Exception.GetBaseException().Message);
        }else{
            Console.WriteLine(String.Format("操作已完成,结果为:{0}", t.Result));
        }
    },TaskContinuationOptions.ExecuteSynchronously);

    wc.DownloadStringAsync(new Uri("http://www.test.com/test/testEAP"));
}

5.  总结

在设计异步编程时,要确定异步操作是I/O-Bound(因I/O阻塞,又称为I/O密集型),还是CPU-Bound(因CPU阻塞,又称为计算密集型),从而更好的选择方式方法。计算密集型并不是任务越多越好,如果任务数量超过CPU的核心数,那么花费在任务切换上的时间就越多,CPU的执行效率就越低。I/O密集型由于任务主要在硬盘读写和网络读写上,所以CPU就可以处理非常多的任务。

之所以有这篇文章,因为没有搜到类似本文,仅需一篇文章记录尽量全面的文章,所以就做了回搬运工,整理汇总一下。

6.  参考信息

  1. https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/#:~:text=For%20more%20information%2C%20see%20Task-based%20Asynchronous%20Pattern%20%28TAP%29.,event%20handler%20delegate%20types%2C%20and%20EventArg%20-derived%20types.
  2. https://www.cnblogs.com/fanfan-90/p/12006157.html
  3. https://www.cnblogs.com/zhili/archive/2013/05/13/TAP.html
  4. https://www.cnblogs.com/jonins/p/9558275.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值