【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)
原文地址:https://www.cnblogs.com/SignX/p/11559959.html