• 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

  • 相关阅读:
    洛谷P1056_排座椅 贪心+桶排思想
    NOIP普及组相关
    洛谷P1464_Function 记忆化搜索
    Xcode的使用技巧
    MAC_XCODE清理
    输入框跟随键盘移动效果的实现
    #pragma的进阶用法
    iOS 逆向
    警告框
    UIImageView设置圆角的方式
  • 原文地址:https://www.cnblogs.com/atomy/p/12047697.html
Copyright © 2020-2023  润新知