ITPub博客

首页 > IT职业 > IT图书 > C#基础提升系列——C#异步编程

C#基础提升系列——C#异步编程

IT图书 作者:adder 时间:2021-09-09 09:36:45 0 删除 编辑
Wait()方法,该方法是一个同步方法,它使调用线程等到当前任务完成。如果当前任务尚未开始执行,则Wait()方法尝试从调度程序中删除该任务,并在当前线程上内联执行该任务。如果无法执行此操作,或者当前任务已经开始执行,则会阻止调用线程,直到任务完成。

我们使用一个简单的代码来测试一下运行效果,如下:

Stopwatch sw = new Stopwatch();
Console.WriteLine("-----开始程序-----");
//开始计时
sw.Start();
//调用方法
Console.WriteLine( AsyncDemo.Greeting("world"));
Console.WriteLine("总执行时间:" + sw.Elapsed.Seconds + "秒");
sw.Stop();
Console.WriteLine("-----结束程序-----");
Console.Read();

控制台在打印出“-----开始程序-----”后,将会花费3秒时间调用Greeting()方法,上述执行结果如下:

-----开始程序-----
Hello,world
总执行时间:3秒
-----结束程序-----

接着我们定义一个将此方法异步化的另一个方法GreetingAsync(),基于任务的异步模式,指定在异步方法名后加上Async后缀,并返回一个任务。如下:

private static Task GreetingAsync(string name)
{
    return Task.Run(() => { return Greeting(name); });
}

上述Task定义了一个返回字符串的任务,该方法返回的是一个任务,该任务返回的是一个字符串。

调用异步方法

可以使用await关键字来调用返回任务的异步方法。使用await关键字需要使用async修饰符声明的方法。

public async static void CallerWithAsync()
{
    string result = await GreetingAsync("异步调用方法");
    Console.WriteLine(result);
}

在GreetingAsync()方法完成前,CallerWithAsync()内的其他代码不会继续执行。但是调用CallerWithAsync()方法的线程并没有阻塞,可以被重用。调用CallerWithAsync()方法如下:

Stopwatch sw = new Stopwatch();
Console.WriteLine("-----开始程序-----");
//开始计时
sw.Start();
AsyncDemo.CallerWithAsync();
Console.WriteLine("总执行时间:" + sw.Elapsed.Seconds + "秒");
sw.Stop();
Console.WriteLine("-----结束程序-----");
Console.Read();

在上述代码中,直接调用了CallerWithAsync()方法,由于外部并不会被阻塞,所以直接会执行AsyncDemo.CallerWithAsync()之后的代码,得到的结果如下:

-----开始程序-----
总执行时间:0秒
-----结束程序-----
Hello,异步调用方法

延续任务(await关键字的实质)

Task类的ContinueWith()方法定义了任务完成后就调用的代码。指派给ContinueWith()方法的委托接收将已完成的任务作为参数传入,使用Result属性可以访问任务返回的结果。

将上述使用await关键字调用的方法CallerWithAsync(),使用Task类的ContinueWith()进行实现:

public static void CallerWithContinuationTask()
{
    Task t1 = GreetingAsync("异步调用方法");
    t1.ContinueWith(t =>
    {
        string result = t.Result;
        Console.WriteLine(result);
    });
}

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

因此该方法的执行效果和CallerWithAsync()方法的执行效果一样,它们输出结果也相同。

同步上下文

在上述的方法CallerWithAsync()中(或CallerWithContinuationTask()方法中),不同的执行阶段使用了不同的线程,一个线程用于调用CallerWithAsync()方法,我们把这个线程称作为外部调用线程或前台线程、主线程,另一个线程执行await关键字后面的代码,或者继续执行ContinueWith()方法内的代码块,我们把这个线程称为方法内部执行线程或后台线程。在使用异步时,必须保证在所有应该完成的后台任务之前,至少有一个前台线程仍然在运行。上述实例中的Console.Read()就是用来保证主线程一直在运行。

有时候,为了执行某些动作,有些应用程序会绑定到指定的线程上。例如,在winform或WPF应用程序中,只有UI线程才能访问UI元素,这将会是一个问题。在未出现async和await之前,需要借助委托来解决这类问题,代码相对比较繁琐。

而使用async和await关键字,当await完成之后,不需要进行任何特别处理,就能访问UI线程。默认情况下,生成的代码就会把线程转换到拥有同步上下文的线程中。WPF设置了DispatcherSynchronizationContext属性,winfrom设置了WindowsFormsSynchronizationContext属性。如果调用异步方法的线程分配了同步上下文,await完成之后将继续执行。默认情况下,使用了同步上下文。如果不使用相同的同步上下文,则必须调用Task方法ConfigureAwait(continueOnCapturedContext:false)。例如, 一个WPF应用程序,其await后面的代码没有用到任何的UI元素。在这种情况下,避免切换到同步上下文会执行的更快。

使用多个异步方法

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

按顺序调用多个异步方法

如果某个异步方法,需要在之前的其他的异步方法执行完后才被调用,就需要使用await关键字。它可以实现一个异步方法依赖另一个异步方法的结果的情况。

public async static void MultipleAsyncMehtods()
{
    string s1 =await GreetingAsync("Mul1");
    string s2 =await GreetingAsync("Mul2");
    Console.WriteLine("Mul:" + s1 + " " + s2);
}

使用组合器

如果异步方法不依赖于其他异步方法,比如无返回值的异步方法或返回值互不联系,可以在异步方法被调用时,不使用await关键字,而是把每个异步方法的返回结果赋值给Task变量,这样运行的就会更快一些。

使用组合器,可以同时并行运行多个异步方法。一个组合器可以接受多个同一类型的参数,并返回同一类型的值。多个同一类型的参数被组合成一个参数来传递。Task组合器接受多个Task对象作为参数,并返回一个Task。

下述示例中使用Task.WhenAll组合器方法,它可以等待,直到两个任务都完成:

public async static void MultipleAsyncMethodsWithCombinators1()
{
    Task t1 = GreetingAsync("mulA");
    Task t2 = GreetingAsync("mulB");
    await Task.WhenAll(t1, t2);
    Console.WriteLine("结果:" + t1.Result + "  " + t2.Result);
}

Task类型的WhenAll方法定义了多个重载版本,如果所有的任务返回相同的类型,可以使用该类型的数组接收await返回的结果。如下:

private async static void MultipleAsyncMethodsWithCombinators2()
{
    Task t1 = GreetingAsync("mulA");
    Task t2 = GreetingAsync("mulB");
    string[] result = await Task.WhenAll(t1, t2);
    Console.WriteLine("结果:" + result[0] + "  " + result[1]);
}

除了WhenAll组合器外,Task类还定义了WhenAny组合器。

  • WhenAll:从WhenAll方法返回的Task,是在所有传入方法的任务都完成了才会返回Task。
  • WhenAny:从WhenAny返回的Task,是在其中一个传入方法的任务完成了就会返回Task。

转换异步模式

并非.NET Framework的所有类都引入了新的异步方法,有些类只提供了BeginXXX方法和EndXXX方法的异步模式,没有提供基于任务的异步模式。我们可以把这些旧的异步模式转换为新的基于任务的异步模式。

为了模拟出BeginXXX和EndXXX这种形式的方法,对之前的同步方法Greeting()进行扩展:

public static string Greeting(string name)
{
    //等待3秒
    Task.Delay(3000).Wait(); //Wait方法用来等待之前的任务完成
    return "Hello," + name;
}
//定义一个委托
private static  Func greetingInvoker = Greeting;

//模拟异步模式
private static IAsyncResult BeginGreeting(
    string name,AsyncCallback callback, object state)
{
    return greetingInvoker.BeginInvoke(name, callback, state);
}
//该方法返回来自于Greeting的结果
private static string EndGreeting(IAsyncResult ar)
{
    return greetingInvoker.EndInvoke(ar);
}
//使用新的基于任务的异步模式进行调用
public static async void ConvertingAsyncPattern()
{
    string s = await Task.Factory.FromAsync(
        BeginGreeting, EndGreeting, "测试", null);
    Console.WriteLine(s);
}

上述实例中,使用TaskFactory类的FromAsync()方法,把使用旧的异步模式的方法转换为基于任务的异步模式的方法(TAP)。其中,Task类型的第一个泛型参数Task定义了调用方法的返回值类型。FromAsync()方法的泛型参数定义了方法的输入类型。FromAsync()方法的前两个参数是委托类型,传入BeginGreeting和EndGreeting方法的声明。紧跟这两个参数后面的是输入参数和对象状态参数。因对象状态没有用到,所以分配null值。因为FromAsync()方法返回Task类型,可以使用await修饰。

CancellationTokenSource类,该类用于发送取消请求。请求发送给引用CancellationToken结构类的任务,其中CancellationToken结构与CancellationTokenSource类相关联。

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

可以将CancellationToken传入异步方法,框架中的某些异步方法提供可以传入CancellationToken的重载版本,来支持取消任务。一旦取消,就会清理资源,之后抛出异常。

注:取消任务之后,都会抛出异常,可以通过调试的方式,在catch块中进行捕获对应的异常信息。

C#概念——异步编程
  • .NET并行处理、并发和异步——异步概述
  • C#指南——C#使用Async和await的异步编程
  • 本文后续会随着知识的积累不断补充和更新,内容如有错误,欢迎指正。

    本文最后一次更新时间:2018-07-23


    来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/1595/viewspace-2805156/ ,如需转载,请注明出处,否则将追究法律责任。

    请登录后发表评论 登录
    全部评论

    注册时间:2019-04-15

    • 博文量
      110
    • 访问量
      724