zoukankan      html  css  js  c++  java
  • 【转载】.NET(C#): Task.Unwrap扩展方法和async Lambda

    .NET(C#): Task.Unwrap扩展方法和async Lambda

     

    返回目录

    Task.Unwrap基本使用

    这个扩展方法定义在TaskExtensions类型中,命名空间在System.Threading.Tasks。Unwrap会把嵌套的Task<Task>或者Task<Task<T>>的结果提取出来。

    就像这样,不用Unwrap的话:

    staticvoid Main(string[] args)
    {
        doo();
    Task.Delay(-1).Wait();
    }
    
    staticasyncvoid doo()
    {
    //运行嵌套的Task
    //Task返回Task<Task<string>>
    //第一个await后result类型为Task<string>
    var result =awaitTask.Run<Task<string>>(() =>
            {
    var task =Task.Run<string>(() =>
                    {
    Task.Delay(1000).Wait();
    return"Mgen";
                    });
    return task;
            });
    
    //第二个await后才会返回string
    Console.WriteLine(await result);
    }

     

    使用Unwrap后,结果可以直接从嵌套Task中提取出来:

    staticasyncvoid doo()
    {
    //运行嵌套的Task
    //Task返回Task<Task<string>>
    //await后类型为Task<string>,Unwrap后result类型为string
    var result =awaitTask.Run<Task<string>>(() =>
            {
    var task =Task.Run<string>(() =>
                    {
    Task.Delay(1000).Wait();
    return"Mgen";
                    });
    return task;
            }).Unwrap();
    
    //不需要await,result已经是string
    Console.WriteLine(result);
    }
     

     

    返回目录

    Task.Factory.StartNew和Task.Run的Unwrap操作

    简单地讲,Task.Factory.StartNew和Task.Run区别之一就有Task.Run会自动执行Unwrap操作,但是Task.Factory.StartNew不会,Task.Run就是Task.Factory.StartNew的更人性化封装,而Task.Factory.StartNew则是原始的执行。(另外关于更多的区别,推荐PFX Team的一篇非常给力的文章:Task.Run vs Task.Factory.StartNew)。

     

    通过代码来验证:

    var task1 =Task.Factory.StartNew(async () =>"Mgen");
    var task2 =Task.Run(async () =>"Mgen");
    
    Console.WriteLine(task1.GetType());
    Console.WriteLine(task2.GetType());

     

    输出:

    System.Threading.Tasks.Task`1[System.Threading.Tasks.Task`1[System.String]]
    System.Threading.Tasks.UnwrapPromise`1[System.String]

     可以看到

    使用Task.Factory.StartNew会返回原始的Task<Task<string>>。但是Task.Run则会直接返回async Lambda的结果,中间的Unwrap操作会自动进行。

     

     

    返回目录

    使用案例:LINQ中的async Lambda

    文章讲述到这里,或许读者在想上述情况会不会很少见?不,任何使用async Lambda的情况都可能会出现上述情况,比如最近在搞一个WinRT的项目,使用LINQ去转换一些数据,但是许多WinRT的API只有异步执行的,这类问题就会出现,下方示例:

     

    我们来进行一个再简单不过的LINQ Select操作,把一堆int转换成string,只不过转换过程是异步的,来看代码:

    staticvoid Main(string[] args)
    {
        doo();
    Task.Delay(-1).Wait();
    }
    
    staticvoid doo()
    {
    //int数据
    var ints =Enumerable.Range(1, 10);
    //转换并输出结果
    foreach (var str in ints.Select(async i =>await Int2StringAsync(i)))
    Console.WriteLine(str);
    }
    
    //异步将int转换成string
    staticasyncTask<string> Int2StringAsync(int i)
    {
    returnawaitTask.Run<string>(() => i.ToString());
    }

     上面代码正确吗?很多人会认为没问题的,Select方法通过一个async Lambda调用异步转换方法,并使用await异步等待结果,那么async Lambda返回string,str类型也是string,最后输出所以字符串。

     

    但事实上程序运行后会输出:

    System.Threading.Tasks.Task`1[System.String]
    System.Threading.Tasks.Task`1[System.String]
    System.Threading.Tasks.Task`1[System.String]
    System.Threading.Tasks.Task`1[System.String]
    System.Threading.Tasks.Task`1[System.String]
    System.Threading.Tasks.Task`1[System.String]
    System.Threading.Tasks.Task`1[System.String]
    System.Threading.Tasks.Task`1[System.String]
    System.Threading.Tasks.Task`1[System.String]
    System.Threading.Tasks.Task`1[System.String]

     

    str变量根本不是string,而是Task<string>。上边的推断错在(上面黄字标注的)“async Lambda返回string”,async Lambda的结果并没有被await,上面的await仅仅是对Int2StringAsync方法的异步等待,而async Lambda本身仍然会返回Task<string>,所以Select会返回一些列的Task<string>。

     

    最简单的解决方案是,在处理结果的时候加上await,如下:

    //str的类型事实上是:Task<string>
    Console.WriteLine(str);

     这是最好的方法(如果能这样的话),因为有了这个await,事实上整个转换过程就异步化了!

     

    当然如果你想在LINQ Select中直接返回结果string而不是Task<string>:

    那么还有一种解决方案就是不使用async Lambda,就不存在嵌套Task的问题,直接在Select中返回异步方法的Task的Result属性:

    //int数据
    var ints =Enumerable.Range(1, 10);
    //Select调用异步方法
    IEnumerable<string> strs = ints.Select(i => Int2StringAsync(i).Result);

     

    如果一定要使用async Lambda,则必须将嵌套的Task进行Unwrap。(当然这里更多的是为了讨论技术本身,实际工作中没必要这么钻牛角尖呵呵。)

    结合上面讲到的知识,使用Task.Factory.StartNew需要进行一个Unwrap,然后返回Task<T>的结果作为Select方法的最终返回值,代码:

    //int数据
    var ints =Enumerable.Range(1, 10);
    //Select调用异步方法
    IEnumerable<string> strs = ints.Select(i =>
    Task.Factory.StartNew(async () =>await Int2StringAsync(i)).Unwrap().Result);

     

    而Task.Run的话,不需要Unwrap:

    IEnumerable<string> strs = ints.Select(i =>
    Task.Run(async () =>await Int2StringAsync(i)).Result);

     

  • 相关阅读:
    Java:线程的六种状态及转化
    Java:多线程概述与创建方式
    小白学Java:RandomAccessFile
    如何用IDEA开启断言
    如何通过IDEA添加serialVersionUID
    小白学Java:I/O流
    更改IDEA相对路径
    小白学Java:File类
    小白学Java:内部类
    Leetcode数组题*3
  • 原文地址:https://www.cnblogs.com/baobaodong/p/4179992.html
Copyright © 2011-2022 走看看