zoukankan      html  css  js  c++  java
  • 【c#基础】异步编程

    1:使用异步编程,方案调用是在后台运行(通常在线程或任务的帮助下),并且不会阻塞调用线程。

    3中不同模式的异步编程:异步模式、基于事件的异步模式和基于任务的异步模式(Task-based Asynchronous Pattern TAP)。TAP是利用async和await关键字来实现。

    主要内容:延续任务和同步上下文。如何取消正在执行的任务。如果后台任务执行时间较长,就有可能需要取消任务。

    委托类型也实现了异步模式。

    一:异步模式

    通常异步模式定义了BeginXXX方法和Endxxx方法,如一个同步方法DownloadString,其异步版本就是BeginDownloadString和EndDownloadString方法。

    BeginXXX方法接口其同步方法的所有输入参数,EndXXX方法四通同步方法的所有输出参数,并按照同步方的放回类型来返回结果。使用异步 模式时,BeginXXX方法还定义了一个AsyncResult,用于接受在异步方法执行完成后调用的委托。

    BeginXXX方法返回IAsyncResult,用于验证调用是否已经完成,并且一直等到方法的执行结束。

    WebClient类没有提供异步模式的实现方式,但是可以用HttpWebRequst类来替代,因为该类通过BeginGetResponse和EndGetResponse方法提供这种模式。

    二:基于时间的异步模式

    OnAsyncEventPattern方法使用了基于事件的异步模式。这个模式由WebClient类实现。

    基于事件的异步模式定义了一个带有“Async”后缀的方法。

     如同步方法DownloadString WebClient类提供一个异步变体方法DownloadStringAsync。

    异步方法完成时,不是定义要调用的委托,而是定义了一个事件。

    基于事件的异步模式的优势在于易于使用。

    在自定义类中实现异步模式的一种方式是使用BackgroundWorker类来实现异步调用同步方法。

    BackergroundWorker类实现了基于事件的异步模式。

    这样使代码更加简单,但是与同步方法调用相比,顺序颠倒了。调用异步方法之前,需要定义这个方法完成时发生什么。

    三:基于任务的异步模式

    .Net4.5中,更新了WebClient类,提供了基于任务的异步模式(TAP)。这个模式定义了一个带有Async后缀的方法,并返回一个Task类型。由于WebClient类已经提供了另一个带Async后缀的方法来实现基于任务的异步模式,因此新方法名为DownloadStringTaskAsync.

     DownloadStringTaskAsync方法声明3为返回Task<string>。但是,不需要声明一个Task<string>类型的变量来设置DownloadStringTaskAsync方法返回的记过。只要声明一个String类型的变量,并使用await关键字。await关键字会解除线程的阻塞,完成其他任务, 当DownloadStringTaskAsync方法完成其后台处理后,就可以从后台任务获取结果。

    string resp=await client.DownloadStringTaskAsync();

    async 关键字创建了一个状态机,类似于yield return 语句

    HttpClient类是在.Net4.5中新添加的类。使用GetAsync方法发出一个异步Get请求。

    然后要读取内容,需要另一个异步方法。ReadAsStringAsync方法返回字符串格式的内容。

    var client=new HttpClinet(参数);
    var response=await client.GetAsync(url);
    string resp=await response.Content.ReadAsStringAsync();

    要利用同步功能创建后台任务,可以使用Task.Run方法。传递给Task.Run方法的代码块在后台线程上运行。

    四:异步编程的基础

    ansync和await关键字知识编译器功能。编译器会用Task类创建代码。如果不使用async和await关键字可以用Task类方法来实现同样的功能。

     static string  Greeting(string name)
            {
                Task.Delay(300).Wait();
                return $"Hello,{name}";
            }
    
            static Task<string> GreetingAsync(string name)
            {
                return Task.Run(()=>Greeting(name));
            }

    五:调用异步方法

    使用await关键字来第暗涌返回任务的异步方法。使用关键字await关键字需要用async修饰符声明方法。在GreetingAsync方法完成前,该方法内的其他代码不会继续执行。但是启动CallerWithAsync方法的线程可以被重用。该线程没有阻塞。

      private static async void CallerWithAsync()
            {
                string result = await GreetingAsync("Stephanie");
                Console.WriteLine(result);
            }

    async修饰符只能用于返回Task或void的方法。程序的入口点,即Main方法不能使用async修饰符。await只能用于返回Task的方法。

    六:延续任务

    GreetingAsync方法返回一个Task<string>对象。该Task<string>对象包含任务创建的信息,并保存到任务完成。Task类的ContinueWith方法定义了任务完成后就调用的代码。

    指派给ContinueWith方法的委托接收将已完成的任务作为参数传入,使用Result属性可以访问任务返回的结果。

     private static void CallerWithContinuationTask()
            {
                Task<string> t1 = GreetingAsync("Stephanie");
                t1.ContinueWith(t =>
                {
                    string result = t1.Result;
                    Console.WriteLine(result);
                });
            }

    编译器把await关键字后的所有代码放进ContinueWith方法的代码块中来转换成await关键字。

    其实就是我们在使用await时,下面的代码编译器会给我们编译进到ContinueWith方法代码块中。

    例子:

     string result = await GreetingAsync("Stephanie");
                Console.WriteLine(result);

    中的Console.WriteLine(result);

    编译器编译成了下图ContinueWiht中的代码块。

     t1.ContinueWith(t =>
                {
                    string result = t1.Result;
                    Console.WriteLine(result);
                });

     七:同步上下文

    如果验证方法找那个使用的线程,会发现CallerWithAsync方法和CallerWithContinuationTask方法。在方法的不同生命阶段使用了不同的线程。一个线程用于调用GreetingAsync方法,另一个线程执行await关键字后面的代码,或者继续执行ContinueWith方法内的代码块。
    如果调用异步方法的线程分配给了同步上下文,await完成之后将继续执行。默认情况下,使用了同步上下文。如果不使用相同的同步上下文,则必须调用Task方法ConfigureAwait(continueOnCapturedContext:false)。看情况 因为切换线程的时候要同步上下文这样会比较耗时。

     八:使用多个异步方法

    在一个异步方法里,可以调用一个或多个异步方法。如何编写代码,取决于一个异步方法的结果是否依赖于另一个异步方法。

    8.1:按顺序调用异步方法

    ;在同一个方法中,多个异步调用完全独立,互不影响,这样就按照顺序调用。

    8.2:使用组合器

    如果异步方法不依赖于其他异步方法,则每个异步方法都不适用await,而是把每个异步方法的返回结果复制给Task变量,就会运行得更快。GreetingAsync方法返回Task<string>。这些方法下i安在可以并行运行。组合器就可以帮助实现并行运行。

    一个组合器可以接受多个同一类型的参数。并返回同一类型的值。

    多个同一类型的参数被组合成一个参数来传递。Task组合器接受多个Task对象作为参数,并返回一个Task。

     private static async void MultipleAsyncMethodsWithCombinatorsl()
            {
                Task<string> t1 = GreetingAsync("Stephanie");
                Task<string> t2 = GreetingAsync("Matthias");
                await Task.WhenAll(t1, t2);
                Console.WriteLine($"Finished both Method  Result 1{t1.Result}: Result 2:{t2.Result}");
            }

    Task类定义了WhenAll和WhenAny组合器。从WhenAll方法返回的Task,是在所有传入方法的任务都完成了才会返回Task.从WhenAny方法返回的Task,是在其中一个传入方法的任务完成了就会返回Task。

    Task.WhenAll可用域返回一个字符串数组,WhenAll有实现了多个重载。

     private static async void MultipleAsyncMethodsWithCombinators2()
            {
                Task<string> t1 = GreetingAsync("Stephanie");
                Task<string> t2 = GreetingAsync("Matthias");
                string[] result=await Task.WhenAll(t1, t2);
                Console.WriteLine($"Finished both Method  Result 1:{result[0]}: Result 2:{result[1]}");
            }

     九:转换异步模式

    在.Net框架中有些类只提供了BeginXXX方法和EndXXX方法的异步模式,没有体哦那个基于任务的异步模式。

    但是:可以把异步模式转换为基于任务的异步模式。

     TaskFactory类定义了FromAsync方法,它可以把使用异步模式的方法转换成基于任务的异步模式的方法(TAP)。

    获取当前线程的Id=Thread.CurrentThread.ManagedThreadId

     private  Func<string, string> greetingInvoker = Greeting;
    
            private  IAsyncResult BeginGreeting(string name, AsyncCallback asyncCallback, object state)
            {
                return greetingInvoker.BeginInvoke(name, asyncCallback, state);
            }
    
            private string EndGreeting(IAsyncResult asyncResult)
            {
                return greetingInvoker.EndInvoke(asyncResult);
            }
    
            private async void ConvertingAsyncPattern()
            {
                string s = await Task<string>.Factory.FromAsync(BeginGreeting,EndGreeting, "Angela", null);
                Console.WriteLine(s);
            }

    十:错误处理

    注意点:返回void的异步方法不会等待,这是因为从async void方法抛出的异常无法捕获。因此异步方法最好返回一个Task类型,处理程序方法或重写的基类方法不受此规则限制。

    1:异步方法的异常处理

    使用await关键字,将其放在try/catch语句中,异步调用ThrowAfter方法后,HandleOneError方法就会释放线程,但它会在任务完成时保持任务的引用。

      private static async void HandleOneError()
            {
                try
                {
                    await ThrowAfter(200, "first");
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
            }

    2:多个异步方法的异常处理

    如果调用两个异步方法,每个都会抛出异常怎么处理?

    并不是按照第一个异步方法处理完后抛出异常,第二个异步方法也抛出异常。

    当第一个异步方法抛出异常后,就没有在继续调用第二个异步方法了。

     private static async void StartTwoTasks()
            {
                try
                {
                    await ThrowAfter(2000, "first");
                    await ThrowAfter(1000, "Second");
                }
                catch (Exception e)
                {
                    Console.WriteLine($"Handled:{e.Message}");
                }
            }

     并行调用这两个ThrowAfter方法。第一个ThrowAfter方法2s后抛出异常,1s后第二个ThrowAfter也抛出异常。使用Task.WhenAll,不管任务是否抛出异常,都会等到两个任务完成。

    但是这个还是只能看见传递给WhenAll方法的第一个任务异常信息。没有先释第二个任务的异常信息,但该任务也在列表中。

     private static async void StartTwoTasksParallel()
            {
                try
                {
                    var t1= ThrowAfter(2000, "first");
                    var t2= ThrowAfter(1000, "Second");
                    await Task.WhenAll(t1, t2);
                }
                catch (Exception e)
                {
                    Console.WriteLine($"Handled:{e.Message}");
                }
            }

    有一种方式可以获取所有任务的异常信息,就是在try块外声明任务变量t1和t2,使他们可以在catch块内访问。 这里可以使用IsFaulted属性检查任务的状态 ,以确认他们是否为出错状态。

    若出现异常,IsFaulted属性会返回true.可以使用Task类的Exception.InnderException访问异常信息本身。另一种获取任务的异常信息更好的方式是下面十一的方法

    十一:使用AggregateException信息(聚合异常信息)

     为了获取所有任务失败的异常信息,可以将Task.WhenAll返回结果写道一个Task变量中。这个任务会一直等到所有任务都结束。否则仍然可能错过抛出的异常。

    Exception属性是AggregateException类型的。这个异常类型定义了InnerExceptions属性,它包含了等待中的所有异常的列表。可以遍历所有异常。

    private static async void ShowAggregateException()
            {
                Task taskResult = null;
                try
                {
                    var t1 = ThrowAfter(2000, "first");
                    var t2 = ThrowAfter(1000, "Second");
                    await (taskResult= Task.WhenAll(t1, t2));
                }
                catch (Exception e)
                {
                    Console.WriteLine($"Handled:{e.Message}");
                    if (taskResult?.Exception.InnerExceptions != null)
                        foreach (var exception in taskResult.Exception.InnerExceptions)
                        {
                            Console.WriteLine($"inner exception {exception.Message}");
                        }
                }
            }

     十二:取消

    在后台任务可以运行很长时间,取消任务就非常有用。

    .Net提供了一种标准机制。这猴子那个机制可用于基于任务的异步模式。

    取消框架基于协助的行为,不是强制性的。

    一个运行时间很长的任务需要检查自己是否被取消,在这种情况下,它的工作就是清理所有已打开的资源,并结束相关工作。

    取消基于CancellationTokenSource类。该类可以用域发送取消请求。

    请求发送给引用CancellationToken类的任务,其中CancellationToken类与CancellationTokenSource类相关联。

    12.1开始取消任务

    1:定义一个私有成员字段CancellationTokenSource变量,该成员用于取消任务,并将令牌传递给应取消的方法。

    private CancellationTokenSource _cts;

    调用

    _cts.Cancel();

    CancellationTokenSource类还支持在指定时间后才取消任务。CancelAfter方法传入一个时间值,单位是毫秒,在该时间过后,就取消任务。

    12.2 使用框架特性取消任务

    HttpClient类的GetAsync方法可以接收CancellationToken参数。用于定期检查是否应取消操作。如果取消,就清理资源,之后抛出OperationCanceledException异常。

    GetAsync(url,_cts.Token);

    12.3 取消自定义任务

    如何取消自定义任务?Task类Run方法提供了重载版本,它也传递CancellationToken参数。

    但是,对于自定义任务,需要检查是否请求了取消操作。

    可以使用IsCancellationRequsted属性检查令牌,在抛出异常之前,如果需要做一些清理工作,最好验证一下是否请求取消操作。如果不需要做清理工作。检查之后,会立即调用ThrowCancellationRequested方法触发异常。

    await Task.Run(()=>{
    //这边_cts 都是上面定义CancellationTokenSource的私有字段。

    _cts.Token.ThrowCancellationRequested(); },_cts.Token)
  • 相关阅读:
    驱动编程:内存管理基本函数
    POOL_TYPE enumeration
    远程服务器下载jenkins上生成的war包,失败原因
    jenkins ssh 报错
    git分组
    免密登录
    jenkins 生成war包后的下载
    redis 连接失败
    nginx+tomcat+https
    jenkins 拉取 git 代码成功版本
  • 原文地址:https://www.cnblogs.com/SignX/p/11559959.html
Copyright © 2011-2022 走看看