async & await 的前世今生

原文:async & await 的前世今生

1. 铺垫基础知识:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp4
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("1. 创建");
            Create();
            Console.WriteLine("2. 线程池");
            ThreadPoolTest();
            Console.WriteLine("3. 传入参数");
            ParaTest();
            Console.WriteLine("4. 返回值");
            ReturnTest();
            Console.WriteLine("5. Semaphore 信号量(锁很好理解,忽视)");
            SemaphoreSlimTest();
            Console.WriteLine("6. 异常");
            //ThreadExceptionTest();
            //TaskExceptionTest();

            Console.Read();
        }

        public static void TaskExceptionTest()
        {
            try
            {
                var task = Task.Run(() => { GoException(); });
                task.Wait();  // 在调用了这句话之后,主线程才能捕获task里面的异常

                // 对于有返回值的Task, 我们接收了它的返回值就不需要再调用Wait方法了
                // GetName 里面的异常我们也可以捕获到
                var task2 = Task.Run(() => { GetNameExeception(); });
                task2.Wait();
                //var name = task2.Result;
            }
            catch (AggregateException ex)
            {
                foreach(Exception ex2 in ex.InnerExceptions)
                Console.WriteLine("Exception!{0}", ex2.Message);
            }
        }

        private static void GetNameExeception()
        {
            throw new Exception("Task Exeception.");
        }

        public static void ThreadExceptionTest()
        {
            try
            {
                new Thread(GoException).Start();
            }
            catch (Exception ex)
            {
                // 其它线程里面的异常,我们这里面是捕获不到的。
                Console.WriteLine("Thread Exception!");
            }
        }
        static void GoException() { throw new Exception("Go Exception"); }


        static SemaphoreSlim _sem = new SemaphoreSlim(3);    // 我们限制能同时访问的线程数量是3
        static void SemaphoreSlimTest()
        {
            for (int i = 1; i <= 5; i++)
            {
                new Thread(Enter).Start(i);
            }
        }

        static void Enter(object id)
        {
            Console.WriteLine(id + " 开始排队...");
            _sem.Wait();
            Console.WriteLine("{0} 开始执行!, 将允许进入 SemaphoreSlim 线程数: {1}", id, _sem.CurrentCount);
            Thread.Sleep(1000 * (int)id);
            Console.WriteLine(id + " 执行完毕,离开!");
            _sem.Release();
        }

        static void ReturnTest()
        {
            // GetDayOfThisWeek 运行在另外一个线程中
            var dayName = Task.Run<string>(() => { return GetDayOfThisWeek(); });
            Console.WriteLine("今天是:{0}", dayName.Result);
        }

        private static string GetDayOfThisWeek()
        {
            return DateTime.Now.DayOfWeek.ToString();
        }

        private static void ParaTest()
        {
            new Thread(Go2).Start("arg1"); // 没有匿名委托之前,我们只能这样传入一个object的参数

            new Thread(delegate ()
            {  // 有了匿名委托之后...
                GoGoGo("arg1", "arg2", "arg3");
            }).Start();

            new Thread(() =>
            {  // 当然,还有 Lambada
                GoGoGo("arg1", "arg2", "arg3");
            }).Start();

            Task.Run(() =>
            {  // Task能这么灵活,也是因为有了Lambda呀。
                GoGoGo("arg1", "arg2", "arg3");
            });
        }

        public static void GoGoGo(string arg1, string arg2, string arg3)
        {
            Console.WriteLine("1.{0}; 2.{1}; 3.{2}",arg1,arg2,arg3);
        }

        private static void ThreadPoolTest()
        {
            Console.WriteLine("我是主线程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
            ThreadPool.QueueUserWorkItem(Go2);
        }

        public static void Go2(object data)
        {
            Console.WriteLine("我是另一个线程:Thread Id {0}, 参数值:{1}"
                , Thread.CurrentThread.ManagedThreadId
                , data
                );
        }

        private static void Create()
        {
            new Thread(Go).Start();  // .NET 1.0开始就有的
            Task.Factory.StartNew(Go); // .NET 4.0 引入了 TPL
            Task.Run(new Action(Go)); // .NET 4.5 新增了一个Run的方法
        }

        public static void Go()
        {
            Console.WriteLine("我是另一个线程");
        }
    }
}

2.  一个小例子认识async & await

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp5
{
    class Program
    {
        static void Main(string[] args)
        {
            Test(); // 这个方法其实是多余的, 本来可以直接写下面的方法
                    // await GetName() 
                    // 但是由于控制台的入口方法不支持async,所有我们在入口方法里面不能 用 await

            Console.WriteLine("Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
            Console.Read();
        }

        static async Task Test()
        {
            // 方法打上async关键字,就可以用await调用同样打上async的方法
            //  await 之后不会开启新的线程(await 从来不会开启新的线程)
            await GetName();
        }

        static async Task GetName()
        {
            // Delay 方法来自于.net 4.5
            await Task.Delay(1000);  // 返回值前面加 async 之后,方法里面就可以用await了
            Console.WriteLine("Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("In antoher thread.....");
        }
    }
}

3. 看清什么时候才创建线程:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp6
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Main Thread Id: {0}\r\n", Thread.CurrentThread.ManagedThreadId);
            Test();
            Console.ReadLine();
        }

        static async Task Test()
        {
            Console.WriteLine("Before calling GetName, Thread Id: {0}\r\n", Thread.CurrentThread.ManagedThreadId);
            var name = GetName();   //我们这里没有用 await,所以下面的代码可以继续执行
                                    // 但是如果上面是 await GetName(),下面的代码就不会立即执行,输出结果就不一样了。
            Console.WriteLine("End calling GetName.\r\n");
            Console.WriteLine("Get result from GetName: {0}", await name);
            Console.WriteLine("end.");
        }

        static async Task<string> GetName()
        {
            // 这里还是主线程
            Console.WriteLine("Before calling Task.Run, current thread Id is: {0}", Thread.CurrentThread.ManagedThreadId);
            return await Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("'GetName' Thread Id: {0}", Thread.CurrentThread.ManagedThreadId);
                return "Jesse";
            });
        }
    }
}





不用 await 关键字, 如何确认Task已执行完?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp6
{
    class Program
    {
        static void Main()
        {
            var task = Task.Run(() => {
                return GetName();
            });

            task.GetAwaiter().OnCompleted(() => {
                // 2 秒之后才会执行这里
                var name = task.Result;
                Console.WriteLine("My name is: " + name);
            });

            Console.WriteLine("主线程执行完毕");
            Console.ReadLine();
        }

        static string GetName()
        {
            Console.WriteLine("另外一个线程在获取名称");
            Thread.Sleep(2000);
            return "Jesse";
        }
    }
}


Task 如何让线程挂起?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp6
{
    class Program
    {
        static void Main()
        {
            var task = Task.Run(() => {
                return GetName();
            });

            var name = task.GetAwaiter().GetResult();
            Console.WriteLine("My name is:{0}", name);

            Console.WriteLine("主线程执行完毕");
            Console.ReadLine();
        }

        static string GetName()
        {
            Console.WriteLine("另外一个线程在获取名称");
            Thread.Sleep(2000);
            return "Jesse";
        }
    }
}


Task.GetAwait()方法会给我们返回一个awaitable的对象,通过调用这个对象的GetResult方法就会挂起主线程,当然也不是所有的情况都会挂起。还记得我们Task的特性么? 在一开始的时候就启动了另一个线程去执行这个Task,当我们调用它的结果的时候如果这个Task已经执行完毕,主线程是不用等待可以直接拿其结果的,如果没有执行完毕那主线程就得挂起等待了。

await 实质是在调用awaitable对象的GetResult方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp6
{
    class Program
    {
        static void Main()
        {
            Test();
            Console.ReadLine();
        }

        static async Task Test()
        {
            Task<string> task = Task.Run(() => {
                Console.WriteLine("另一个线程在运行!");  // 这句话只会被执行一次
                Thread.Sleep(2000);
                return "Hello World";
            });

            // 这里主线程会挂起等待,直到task执行完毕我们拿到返回结果
            var result = task.GetAwaiter().GetResult();
            Console.WriteLine(result);
            // 这里不会挂起等待,因为task已经执行完了,我们可以直接拿到结果
            var result2 = await task;
            Console.WriteLine(result2);
        }
    }
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值