C#中的异步多线程3 await和异步控制流

异步方法的结构包含有3个不同的区域:

async Task<int> CountCharactersAsync(int id,string site)
{
    //section 1 第一个await表达式之前的部分
    Console.WriteLine("Starting CounCharacters");
    WebClient wc=new WebClient();
    //section 2 await表达式
    string result=await wc.DownloadStringTaskAsync(new Uri(site));
    //section 3 后续部分
    Console.WriteLine("CountCharacters Completed");
    return result.Length;
}

section 1:从方法开头到第一个await表达式之前的所有代码,这一部分应只包含少量且无需长时间处理的代码。

section 2:await表达式,表示将被异步执行的任务。

section 3:后续部分,await表达式之后出现的方法中的其余代码。包括其执行环节,所在线程信息,目前作用域内的变量值,以及当await表达式完成后要重新执行所需的其他信息。

一、异步方法控制流

       一个异步方法的控制流,从第一个await表达式之前的代码开始,同步执行,直到遇到第一个await。这一区域实际上在第一个await表达式处结束,此时await任务还没有完成(一般没有完成)。当await任务完成时,方法将继续同步执行。如果还有其他await,就重复上述过程。(异步方法内部)

       当到达await表达式时,异步方法将控制返回到调用方法(跳出异步方法至调用方法)。如果方法的返回类型为Task或Task<T>类型,就讲创建一个Task对象,表示需异步完成的任务和后续,然后将该Task返回到调用方法。 

       异步方法内部:

       1、异步执行await表达式的空闲任务

       2、当await表达式完成时,执行后续部分。后续部分本身也可能包含其他await表达式,这些表达式也将按照相同方式处理,即异步执行await表达式,然后执行后续部分。

       3、当后续部分遇到return语句或到达方法末尾时,将:

             3-1、如果方法返回类型为void,控制流退出

             3-2、如果方法返回类型为Task,后续部分设置Task的属性并退出。如果返回类型为Task<T>,后续部分还将设置Task对象的Result属性。

       同时,调用方法中的代码将继续其进程,从异步方法获取Task对象。当需要实际值时,就引用Task对象的Result属性。届时,如果异步方法设置了该属性,调用方法就能获得该值并继续。否则,将暂停并等待该属性被设置,然后再继续执行。

        具体的各种控制流

        1、调用方法调用异步方法——同步执行代码,没有await表达式-返回调用方法继续执行

        2、调用方法调用异步方法——同步执行代码,有await表达式——await表达式任务没有完成——创建空闲任务,创建后续部分,返回到调用方法——A、返回到调用方法的部分会继续执行。B、异步方法会执行await的空闲任务——执行后续部分——设置返回的Task状态和返回值——退出

        3、调用方法调用异步方法——同步执行代码,有await表达式——await表达式任务完成——同步执行代码,有await表达式.

        注意:同步方法第一次遇到await时,所返回的对象类型并非await表达式的返回类型,而是同步方法头中的返回类型,它与await表达式的返回值类型没有任何关系。

        例如:  

private async Task<int> CountCharactersAsync(string site)
{
   WebClient wc=new WebClient();
   string result=await wc.DownloadStringTaskAsync(new Uri(site));
   return result.Length;
}

         同步方法第一次遇到await表达式时所返回的对象其实是Task<int>,而并不是await表达式的string。Task<int>是异步方法的返回类型。而实际上return语句“返回”一个结果或到达异步方法结尾时,它并没有返回真正的某个值,而只是单纯的退出了。

二、await表达式

        await表达式指定了一个异步执行的任务。其语法由await关键字和一个空闲对象/任务(Task)组成。这个任务可能是一个Task类型的对象,也可能不是。默认情况下,这个任务在当前线程异步执行。

        一个空闲对象/任务即是一个awaitable类型的实例。awaitable类型是指包含GetAwaiter方法的类型,该方法没有参数,返回一个awaiter类型的对象。awaiter类型包括以下成员:

       1、bool IsCompleted{get;}

       2、void OnCompleted(Action);

       3、void GetResult();或T GetResult();

       大部分时候,不需要构建自己的awaitable,而应该使用Task类,它是awaitable类型。.Net 4.5 BCL中有很多异步方法,返回Task<T>类型对象。将这些放至await表达式中,它们会在当前线程中异步执行。

       当有需要时如何编写自己的Task<T>类型方法?

       最简单的办法是使用Task.Run创建一个Task,其原型如下:Task Run(Func<TReturn> func)

       要将自己的方法传递给Task.Run方法,要注意的是Task.Run是在不同的线程上运行方法,需要基于该方法创建一个委托。3种不同写法:

    class MyClass
    {
        public int Get10()
        {
            return 10;
        }
        public async Task DoWorkAsync()
        {
            //Func委托,最后一个参数是返回类型,可以有0-16个其他参数
            Func<int> ten = Get10;
            int a = await Task.Run(ten);
            int b = await Task.Run(new Func<int>(Get10));
            int c = await Task.Run(() => { return 10; });
            Console.WriteLine("{0} {1} {2}", a, b, c);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Task t = (new MyClass()).DoWorkAsync();
            t.Wait();
            Console.ReadLine();
        }
    }

上面的例子中,Task.Run的签名是以Func<TResult>为参数。其他的重载还包括:

Task.Run(Action action)

Task.Run(Action action,CancellationToken token)

Task<TResult>.Run(Func<TResult> function)

Task<TResult>.Run(Func<TResult> function,CancellationToken token)

Task.Run(Func<Task> function)

Task.Run(Func<Task> function,CancellationToken token)

Task<TResult>.Run(Func<Task<TResult>> function)

Task<TResult>.Run(Func<Task<TResult>> function,CancellationToken token)

当遇到特殊的方法不符合时,可以使用接收的Func委托的形式创建一个Lambda函数,这个函数只用于运行特殊方法。例如:Task.Run(()=>GetSum(4,5));

posted @ 2020-05-22 16:44  NicolasLiaoRan  阅读(418)  评论(1)    收藏  举报