zoukankan      html  css  js  c++  java
  • C#线程学习笔记九:async & await入门二

        一、异步方法返回类型

        只能返回3种类型(void、Task和Task<T>)。

        1.1、void返回类型:调用方法执行异步方法,但又不需要做进一步的交互。

        class Program
        {
            static void Main(string[] args)
            {
                #region async & await入门二之void返回类型
                AddAsync(1, 2);
                Thread.Sleep(1000);
                Console.WriteLine("AddAsync方法执行完成。");
                Console.Read();
                #endregion
            }
    
            /// <summary>
            /// 加法
            /// </summary>
            /// <param name="n"></param>
            /// <param name="m"></param>
            /// <returns></returns>
            private static int Add(int n, int m)
            {
                return n + m;
            }
    
            /// <summary>
            /// 异步加法
            /// </summary>
            /// <param name="n"></param>
            /// <param name="m"></param>
            private static async void AddAsync(int n, int m)
            {
                int val = await Task.Run(() => Add(n, m));
                Console.WriteLine($"Result: {val}");
            }
        }
    View Code

        运行结果如下:

        1.2、Task返回类型:调用方法不需要从异步方法中取返回值,但是希望检查异步方法的状态,那么可以选择可以返回Task类型的对象。不过,就算异步方法中包含

    return语句,也不会返回任何东西。

        class Program
        {
            static void Main(string[] args)
            {
                #region async & await入门二之Task返回类型
                Task task = TaskAddAsync(1, 2);
                task.Wait();
                Console.WriteLine("TaskAddAsync方法执行完成。");
                Console.Read();
                #endregion
            }
    
            /// <summary>
            /// 加法
            /// </summary>
            /// <param name="n"></param>
            /// <param name="m"></param>
            /// <returns></returns>
            private static int Add(int n, int m)
            {
                return n + m;
            }
    
            /// <summary>
            /// 异步加法
            /// </summary>
            /// <param name="n"></param>
            /// <param name="m"></param>
            /// <returns></returns>
            private static async Task TaskAddAsync(int n, int m)
            {
                int val = await Task.Run(() => Add(n, m));
                Console.WriteLine($"Result: {val}");
            }
        }
    View Code

        运行结果如下:

        1.3、Task<T>返回类型:调用方法要从调用中获取一个T类型的值,异步方法的返回类型就必须是Task<T>。调用方法从Task的Result属性获取的就是T类型的值。

        class Program
        {
            static void Main(string[] args)
            {
                #region async & await入门二之Task<T>返回类型
                Task<int> task = TaskTAddAsync(1, 2);
                task.Wait();
                Console.WriteLine($"Result: {task.Result}");
                Console.Read();
                #endregion
            }
    
            /// <summary>
            /// 加法
            /// </summary>
            /// <param name="n"></param>
            /// <param name="m"></param>
            /// <returns></returns>
            private static int Add(int n, int m)
            {
                return n + m;
            }
    
            /// <summary>
            /// 异步加法
            /// </summary>
            /// <param name="n"></param>
            /// <param name="m"></param>
            /// <returns></returns>
            private static async Task<int> TaskTAddAsync(int n, int m)
            {
                int val = await Task.Run(() => Add(n, m));
                return val;
            }
        }
    View Code

        运行结果如下:

        二、异步方法控制流

     

        异步方法的控制流:

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

        2)await表达式执行完毕并释放线程,然后从线程池中申请新的线程继续执行表达式后续部分。如再遇到await表达式,按相同情况进行处理。

        3)到达末尾或遇到return语句时,根据返回类型可以分三种情况:

            <1>void:退出控制流。

            <2>Task:设置Task的属性并退出。

            <3>Task<T>:设置Task的属性和返回值(Result属性)并退出。

        4)调用方法将继续执行。需要注意的是:若调用方法需要用到异步方法结果的时候,会暂停等到Task对象的Result属性被赋值后才会继续执行。

        【难点】

        1)第一次遇到await所返回对象的类型,是同步方法头的返回类型,跟await表达式的返回值没有关系。

        2)到达异步方法的末尾或遇到return语句,它并没有真正的返回一个值,而是退出了该方法。

        三、异步方法await表达式

        在大多数的时候,await一般和Task一起使用,用await表达式来指定一个异步执行的任务,以实现更高的灵活性和效率。

        可以用于await运算符的对象要求如下:

        1)有一个GetAwaiter()方法或扩展方法,它返回一个实现了INotifyCompletion接口的awaiter对象(或结构)

        2)返回的awaiter对象(或结构)要求实现如下方法:

            <1>void OnCompleted(Action continuation)

            <2>bool IsCompleted { get ; }

            <3>TResult GetResult() //TResult也可以是void类型

        下面简单的介绍一下await运算符是如何实现异步操作的?

        例如,对于如下代码

        var j = await 3;

        DoContinue(j);

        在编译的时候会被编译器翻译为类似如下流程的代码(注:这个只是功能类似的简化流程示例,实际并非如此)。

        var awaiter = 3.GetAwaiter();
        var continuation = new Action(() =>
        {
            var j = awaiter.GetResult();
            DoContinue(j);
        });

        if (awaiter.IsCompleted)
            continuation();
        else
            awaiter.OnCompleted(continuation);

        有了这个基础,我们就可以对一个int型的变量实现await操作了:

        /// <summary>
        /// MyAwaiter类
        /// </summary>
        class MyAwaiter : System.Runtime.CompilerServices.INotifyCompletion
        {
            public bool IsCompleted { get { return false; } }
    
            public void OnCompleted(Action continuation)
            {
                Console.WriteLine("OnCompleted");
                ThreadPool.QueueUserWorkItem(_ =>
                {
                    Thread.Sleep(1000);
                    result = 300;
                    continuation();
                });
            }
    
            int result;
            public int GetResult()
            {
                Console.WriteLine("GetResult");
                return result;
            }
        }
    
        /// <summary>
        /// MyAwaiter类扩展
        /// </summary>
        static class MyAwaiterExtend
        {
            public static MyAwaiter GetAwaiter(this int i)
            {
                return new MyAwaiter();
            }
        }
    
        class Program
        {        
            static void Main(string[] args)
            {
                #region async & await入门二之await如何实现异步
                AwaitAchieveAsync();
                Console.Read();
                #endregion
            }
    
            /// <summary>
            /// AwaitAchieveAsync异步方法
            /// </summary>
            public static async void AwaitAchieveAsync()
            {
                var j = await 3;
                Console.WriteLine(j);
            }
        }
    View Code

        运行结果如下:

        这样我们就能看出await是如何实现call/cc式的异步操作了:

        1)编译器把后续操作封装为一个Action对象continuation,传入awaiter的OnCompleted函数并执行。

        2)awaiter在OnCompleted函数中执行异步操作,并在异步操作完成后(一般是异步调用的回调函数)执行continuation操作。

        3)continuation操作的第一步就是调用awaiter.GetResult()获取异步操作的返回值,并继续执行后续操作。

        看到这里,相信大家对await的机制已经有了简单的认识,也就不难理解为什么AsyncTargetingPack能使得.NET 4.0程序也支持await操作了——该库在

    AsyncCompatLibExtensions类中对Task类提供了GetAwaiter扩展函数而已。

        以上是通过创建自己的awaitable类型来演示await实现异步的过程,实际上,你并不需要构建自己的awaitable类型,只需要使用Task类即可。每一个任务都是awaitable

    类的实例。

        下面代码演示使用Task.Run()来创建一个Task。

        class Program
        {
            static void Main(string[] args)
            {
                #region async & await入门二之使用Task.Run创建Task
                var task = GetGuidAsync();
                task.Wait();
                Console.Read();
                #endregion
            }
    
            /// <summary>
            /// 获取 Guid
            /// </summary>
            /// <returns></returns>
            private static Guid GetGuid()
            {
                return Guid.NewGuid();
            }
    
            /// <summary>
            /// 异步获取Guid
            /// </summary>
            /// <returns></returns>
            private static async Task GetGuidAsync()
            {
                var myFunc = new Func<Guid>(GetGuid);
                var t1 = await Task.Run(myFunc);
                var t2 = await Task.Run(new Func<Guid>(GetGuid));
                var t3 = await Task.Run(() => GetGuid());
                var t4 = await Task.Run(() => Guid.NewGuid());
    
                Console.WriteLine($"t1: {t1}");
                Console.WriteLine($"t2: {t2}");
                Console.WriteLine($"t3: {t3}");
                Console.WriteLine($"t4: {t4}");
            }
        }
    View Code

        上面4个Task.Run() 都是采用了Task.Run(Func<TResult> func) 形式来直接或间接调用Guid.NewGuid()。

        运行结果如下:

        Task.Run()支持4种不同的委托类型:Action、Func<TResult>、Func<Task> 和 Func<Task<TResult>>

        class Program
        {
            static void Main(string[] args)
            {
                #region async & await入门二之使用Task.Run支持的4种委托类型
                var task = GetGuidFrom4Async();
                task.Wait();
                Console.Read();
                #endregion
            }
    
            /// <summary>
            /// 获取 Guid
            /// </summary>
            /// <returns></returns>
            private static Guid GetGuid()
            {
                return Guid.NewGuid();
            }
    
            /// <summary>
            /// 异步获取Guid(Task.Run支持的4种委托类型)
            /// </summary>
            /// <returns></returns>
            private static async Task GetGuidFrom4Async()
            {
                await Task.Run(() => { Console.WriteLine(Guid.NewGuid()); });                   //Action
                Console.WriteLine(await Task.Run(() => Guid.NewGuid()));                        //Func<TResult>
                await Task.Run(() => Task.Run(() => { Console.WriteLine(Guid.NewGuid()); }));   //Func<Task>
                Console.WriteLine(await Task.Run(() => Task.Run(() => Guid.NewGuid())));        //Func<Task<TResult>>
            }
    View Code

        运行结果如下:

        四、异步方法暂停

        Task.Delay() 与Thread.Sleep不同的是,它不会阻塞线程,意味着线程可以继续处理其它工作。

        class Program
        {
            static void Main(string[] args)
            {
                #region async & await入门二之异步方法暂停
                Console.WriteLine($"{nameof(Main)} start.");
                DoAsync();
                Console.WriteLine($"{nameof(Main)} end.");
                Console.Read();
                #endregion
            }
    
            /// <summary>
            /// 异步执行
            /// </summary>
            private static async void DoAsync()
            {
                Console.WriteLine($"{nameof(DoAsync)} start.");
                await Task.Delay(500);
                Console.WriteLine($"{nameof(DoAsync)} end.");
            }
        }
    View Code

        运行结果如下:

        五、异步方法取消

        CancellationToken和CancellationTokenSource这两个类允许你终止执行异步方法。

     1)CancellationToken对象包含任务是否被取消的信息。如果该对象的属性IsCancellationRequested为true,任务需停止操作并返回。该对象操作是不可逆的,且只能

    使用(修改)一次,即该对象内的IsCancellationRequested属性被设置后,就不能改动。

     2)CancellationTokenSource可创建CancellationToken对象,调用CancellationTokenSource对象的Cancel方法,会使该对象的CancellationToken属性

    IsCancellationRequested设置为true。

        【注意】调用CancellationTokenSource对象的Cancel方法,并不会执行取消操作,而是会将该对象的CancellationToken属性IsCancellationRequested设置为true。

        class Program
        {
            static void Main(string[] args)
            {
                #region async & await入门二之异步方法取消
                CancellationTokenSource source = new CancellationTokenSource();
                CancellationToken token = source.Token;
    
                var task = ExecuteAsync(token);
                Console.WriteLine($"{nameof(token.IsCancellationRequested)}: {token.IsCancellationRequested}");
    
                Thread.Sleep(6000); //挂起6秒
                source.Cancel();    //传达取消请求
    
                task.Wait(token);   //等待任务执行完成
                Console.WriteLine($"{nameof(token.IsCancellationRequested)}: {token.IsCancellationRequested}");
    
                Console.Read();
                #endregion
            }
    
            /// <summary>
            /// 异步执行
            /// </summary>
            /// <param name="token"></param>
            /// <returns></returns>
            private static async Task ExecuteAsync(CancellationToken token)
            {
                if (token.IsCancellationRequested)
                {
                    return;
                }
    
                await Task.Run(() => CircleOutput(token), token);
            }
    
            /// <summary>
            /// 循环输出
            /// </summary>
            /// <param name="token"></param>
            private static void CircleOutput(CancellationToken token)
            {
                Console.WriteLine($"{nameof(CircleOutput)} 方法开始调用:");
    
                const int num = 5;
                for (var i = 0; i < num; i++)
                {
                    if (token.IsCancellationRequested)  //监控CancellationToken
                    {
                        return;
                    }
    
                    Console.WriteLine($"{i + 1}/{num} 完成");
                    Thread.Sleep(1000);
                }
            }
        }
    View Code

        运行结果如下:

        六:异步方法异常处理

        class Program
        {
            static void Main(string[] args)
            {
                #region async & await入门二之异步方法异常处理
                var task = ExceptionAsync();
                task.Wait();
    
                Console.WriteLine($"{nameof(task.Status)}: {task.Status}");             //任务状态
                Console.WriteLine($"{nameof(task.IsCompleted)}: {task.IsCompleted}");   //任务完成状态标识
                Console.WriteLine($"{nameof(task.IsFaulted)}: {task.IsFaulted}");       //任务是否有未处理的异常标识
    
                Console.Read();
                #endregion
            }
    
            /// <summary>
            /// 异常操作
            /// </summary>
            /// <returns></returns>
            private static async Task ExceptionAsync()
            {
                try
                {
                    await Task.Run(() => { throw new Exception(); });
                }
                catch (Exception)
                {
                    Console.WriteLine($"{nameof(ExceptionAsync)} 出现异常。");
                }
            }
        }
    View Code

        后记:一、四、五、六也算是C#线程学习笔记七:Task详细用法的一些补充,其它的用法与笔记七的用法差不多的,这里就不再赘述了。

        参考自:

        https://www.cnblogs.com/liqingwen/p/5844095.html

        https://www.cnblogs.com/TianFang/archive/2012/09/21/2696769.html

        https://www.cnblogs.com/liqingwen/p/5866241.html

  • 相关阅读:
    OpenJDK源码研究笔记(十二):JDBC中的元数据,数据库元数据(DatabaseMetaData),参数元数据(ParameterMetaData),结果集元数据(ResultSetMetaDa
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 241 为运算表达式设计优先级
    Java实现 LeetCode 241 为运算表达式设计优先级
    Java实现 LeetCode 241 为运算表达式设计优先级
  • 原文地址:https://www.cnblogs.com/atomy/p/12047697.html
Copyright © 2011-2022 走看看