• C#中的异步陷阱


    本文主要介绍异步编程中,常见的异步陷阱:

    1、Async没有异步运行

    我们来看下面代码,猜测他是如何打印出下面的三个字符串:

    /*Gotcha #1: Async does not run asynchronously*/
    static void Main(string[] args)
    {
        Console.WriteLine("begin" + "" + DateTime.Now.ToString() + "");
        var child = Gotcha1.WorkThenWait();
        Console.WriteLine("started" + "" + DateTime.Now.ToString() + "");
        child.Wait();
        Console.WriteLine("completed" + "" + DateTime.Now.ToString() + "");
        Console.ReadKey();
    }
    public static async Task WorkThenWait()
    {
        Thread.Sleep(5000);
        Console.WriteLine("work" + "" + DateTime.Now.ToString() + "");
        await Task.Delay(10000);
    }

    看这段代码,如果你猜想,他会按顺序打印出“begin“,”started”,“work”,“completed”,那样的话,你就错了。这段代码会输出“begin“,“work”,“started”,“completed”。

    可以看出来,本段代码本来的猜想是,由于WorkThenWait()方法中包含繁重耗时的任务(这里为Thread.Sleep(5000)),所以打算让他异步执行这个方法,但是,可以看出问题出在await关键字用在了该段繁重耗时任务之后,所以后面的child.Wait()方法只是等待了Task.Delay(10000)的执行。

    执行结果如下:

     2、忽略结果

    我们来看下面代码,猜测他是如何执行的,是否会等待:

    /*Gotcha #2: Ignoring results*/
    static void Main(string[] args)
    {
        Gotcha2.Handler();
        Console.ReadKey();
    }
    public static async Task Handler()
    {
        Console.WriteLine("Before");
        Task.Delay(5000);
        Console.WriteLine("After");
    }

    看这段代码,你是否期待它会打印“Before”,等待5秒之后,再打印“After”?当然,又错了。他会立即打印两条字符串,直接没有任何等待。问题出在,虽然Task.Delay返回了Task返回值,但是我们忘记了使用await关键字去等待直到他完成。

    执行结果如下:

     3、Async void方法

    我们来看下面代码,判断下是否能成功打印出异常信息:

    /*Gotcha #3: Async void methods*/
    static void Main(string[] args)
    {
        Gotcha3.CallThrowExceptionAsync();
        Console.ReadKey();
    }
    
    private static async void ThrowExceptionAsync()
    {
        throw new InvalidOperationException();
    }
    
    public static void CallThrowExceptionAsync()
    {
        try
        {
            ThrowExceptionAsync();
        }
        catch (Exception)
        {
            Console.WriteLine("Failed");
        }
    }

    你是否觉得这段代码会打印“Failed”?当然,这个异常没有被捕获到,因为ThrowExceptionAsync方法开始执行并立即返回(这个异常发生在background thread内部)。问题根源在于:对于例如 async void Foo(){...},这种方法,C#编译器将生成一个返回void的方法,然后它会在后台创建一个task并执行。这意味着你无法知道这项工作实际何时发生。

     4、Async void lambda函数

    如果你将异步lambda函数作为委托传递给某个方法时。在这种情况下,C#编译器将从委托类型推断方法的类型。如果使用Action delegate,则编译器生成async void function(这种后台启动线程工作并返回void)。如果使用Func<Task> delegate,编译器讲生成返回Task的function。

    我们来看下面代码,判断这段代码是在5秒之后执行完(等待所有的Task完成sleeping),或者它立即完成了?

    /*Gotcha #4: Async void lambda functions*/
    static void Main(string[] args)
    {
        Gotcha4.Test();
        Console.WriteLine("ok");
    }
    
    public  static void Test()
    {
        Parallel.For(0, 10, async i => {
            await Task.Delay(5000);
        });
    }

    当然,直接看For无法判断出他是否等待,我们借助小工具看下如下代码的源码:

    public void TestMethod()
    {
        Parallel.For(0, 10, async i => {
            await Task.Delay(5000);
        });
    }
    // AsyncGotchasLib.Class1
    public void TestMethod()
    {
        int arg_23_0 = 0;
        int arg_23_1 = 10;
        Action<int> arg_23_2;
        if ((arg_23_2 = Class1.<>c.<>9__0_0) == null)
        {
            arg_23_2 = (Class1.<>c.<>9__0_0 = new Action<int>(Class1.<>c.<>9.<TestMethod>b__0_0));
        }
        Parallel.For(arg_23_0, arg_23_1, arg_23_2);
    }

    可以看到For中,lambda函数最终编译为了Action Delegate,因此这段代码不会等待5秒,会立即执行完成,它的等待会在后台线程执行。

     5、嵌套任务

    看下面代码,问题是,下面两个print直接会不会等待5秒:

    /*Gotcha #5: Nesting of tasks*/
    static void Main(string[] args)
    {
        Gotcha5.Test();
        Console.ReadKey();
    }
    
    public async static void Test()
    {
        Console.WriteLine("Before");
        await Task.Factory.StartNew(
          async () => { await Task.Delay(5000); Console.WriteLine("后台线程等待5秒后"); });
        Console.WriteLine("After");
    }

    同样,相当意外,并没有在两个打印(“Before”,“After”)之间等待。为什么?StratNew 方法接受一个委托,并返回一个Task<T>,其中T表示又delegate返回的类型。在这个方法中,这个delegate返回Task,因此,我们获得的结果为Task<Task>。使用await关键字仅仅等待外部Task的完成(它将立即返回内部task),这代表内部task将被忽略,内部task将在后台线程执行。

  • 相关阅读:
    Uboot的串口下载文件命令:loads / loadb / loady
    U-Boot中关于TEXT_BASE,代码重定位,链接地址相关说明
    u-boot-2014.04分析
    Spring MVC + Java 多文件上传及多文件中转上传
    Java 文件上传中转
    backdrop-filter 和filter 写出高斯模糊效果 以及两者区别
    解读浮动闭合最佳方案:clearfix
    JavaScript ES6中export及export default的区别
    webpack配置常用loader加载器
    chrome jsonView插件安装
  • 原文地址:https://www.cnblogs.com/SimplePerson/p/7395513.html
Copyright © 2020-2023  润新知