5.1 async异步关键字

thread->threadpool(APM,EAP)->TPL->异步函数 asynchronous function,使用async关键字标注一个方法。

  1. async Task<string> GetStringAsync(){
  2. await Task.Delay(TimeSpan.FromSeconds(2));
  3. return "Albert";
  4. }

如果方法本省无需异步或并行运行,标记为async无意义,async方法有显著的性能损失,比不带该关键字的方法慢上40-50倍。

5.2 使用await操作符获取异步任务结果

async同步方法,有此关键字,在方法内部才能使用await等待异步执行结果,await会去等待后面的Task执行完毕并返回结果。

namespace AsyncAndAwait {
    class Program {
        static void Main(string[] args) {
            CalcSomeOperate();
            Thread.Sleep(5);
            Console.ReadLine();
        }

        static async Task<string> CalcSomeOperate() {
            Console.WriteLine("======开启异步方法=======");
            string result = await Task<string>.Run(() => { return $"helloworld {Thread.CurrentThread.ManagedThreadId}"; });
            Console.WriteLine($"我是程序运行结果:{result}");
            await Task.Run(()=>Console.WriteLine($"Hello {Thread.CurrentThread.ManagedThreadId}"));
            return result;
        }    
    }
}

5.3 在Lambda表达式中使用await操作符

这边尝试了C#6.0引入的自动属性初始化以及C#7.0引入的元组概念(swift里面也有)
在AsynchronousProcessing()方法里面,首先声明了一个Func委托,输入参数string类型,输出参数Task,async修饰的方法只可能返回三种类型Task,Task,void(用于顶层UI)。name作为参数传入goes to 到后面的表达式中,此lambda表达式用async修饰,返回string类型,自动被编译器封装为Task类型。string result = await asyncLambda(“async lamba”)开始异步调用,并将返回值给result。await有先后执行顺序,最后将结果打印出来。

namespace LambdaAwait {
    class Program {
        public int TestValue { get; set; } = 3; //C#6.0 .NET Framework4.6引入
        static void Main(string[] args) {
            var test = TestMethod();
            Console.WriteLine(test.Age);
            Console.WriteLine(test.Name);
            Task t = AsynchronousProcessing();
            t.Wait();
            Console.ReadLine();
        }

        //C#7.0 元组,支持直接解析(int a,string b)=TestMethod
        static (int Age,string Name) TestMethod() {
            return (24,"Albert");
        }

        //Func<输入参数,输入参数...输出参数>
        static async Task AsynchronousProcessing() {
            Func<string, Task<string>> asyncLambda = async name => {
                await Task.Delay(TimeSpan.FromSeconds(2));
                return $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}" +
                $"Is thread pool:{CurrentThread.IsThreadPoolThread}";
            };

            string result = await asyncLambda("async lambda");
            Console.WriteLine(result);
        }
    }
}

5.4 对连续的异步任务使用

TPL对于连续任务,首先是创建一个容器任务,而后执行延续任务,通过延续任务来执行多任务。异步函数通过await加 async声明的函数直接进入任务模式,要注意的是异步并不总是并行执行

namespace ContinuousAwait {
    class Program {
        static void Main(string[] args) {
            Task t = AsynchronyWithTPL();
            t.Wait();

            t = AsynchronyWithWait();
            t.Wait();

            Console.ReadLine();
        }

        static Task AsynchronyWithTPL() {
            var containerTask = new Task(() => {
                Task<string> t = GetInfoAsync("TPL1");
                t.ContinueWith(task => {
                    Console.WriteLine(t.Result);
                    Task<string> t2 = GetInfoAsync("TPL2");
                    t2.ContinueWith(innerTask => Console.WriteLine(innerTask.Result),
                        TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent);
                }, TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent);
            });
            containerTask.Start();
            return containerTask;
        }

        static async Task AsynchronyWithWait() {
            try {
                //异步并不总是并行执行,await顺序执行。
                string result = await GetInfoAsync("Async 1");
                Console.WriteLine(result);
                result = await GetInfoAsync("Async 2");
                Console.WriteLine(result);
            }
            catch (Exception ex) {

                Console.WriteLine(ex.Message); ;
            }
        }

        static async Task<string> GetInfoAsync(string name) {
            Console.WriteLine($"Task {name} started.");
            await Task.Delay(TimeSpan.FromSeconds(2));
            if(name == "TPL2") {
                throw new Exception("Boom!");
            }
            return $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}" +
                $"Is thread pool thread:{CurrentThread.IsThreadPoolThread}";
        }
    }
}

5.5 对并行执行的异步任务使用await操作符

并行执行,通过Task.WhenAll来组合任务。这里需要注意的是尽量使用await Task.Delay来设置延时,而非Thread.Sleep方法,第一种方式可以有效减少线程的消费,是创建高伸缩性的服务器程序的关键。

namespace ParallelAsyncTask {
    class Program {
        static void Main(string[] args) { //C#6.0支持在main方法中加async了
            MainMethod();
            Console.ReadLine();
        }

        static async Task<string> GetInfoAsync(string name) {
            Console.WriteLine($"Task {name} started.");
            await Task.Delay(TimeSpan.FromSeconds(2));//启动定时器,等定时器结束调用线程池里的线程
            return $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}" +
                $"Is thread pool thread:{CurrentThread.IsThreadPoolThread}";
        }

        static async Task MainMethod() {
            Task<string> t1 = GetInfoAsync("Task1");
            Task<string> t2 = GetInfoAsync("Task2");

            string[] result = await Task.WhenAll(t1, t2);
            foreach (var item in result) {
                Console.WriteLine(item);
            }
            Console.ReadLine();
        }
    }
}

5.6 ConfigureAwait(continueOnCapturedContext:false)

//ConfigureAwait(true) 代码由同步执行进入异步执行时,当前同步执行的线程上下文信息会被
//捕捉并保存到SynchronizaitonContext中,供异步执行中使用,在异步执行完成后(await之后的代码)
//的同步执行中使用,在await之后虽然是同步执行,但是发生了线程切换,产生了开销,但此开销很小,几乎
//不会影响性能问题

//ConfigureAwait(flase)不进行线程上下文信息的捕捉,async方法中与await之后的代码执行时就无法获取await
//之前的线程的上下文信息
await捕捉同步上下文,会有一定的线程切换资源消耗,同步上下文需要3s,不同步2ms
image.png

namespace WinformAvoidSynchronousContent
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            this.textBox1.Text = "Calculating....";
            TimeSpan resultWithContent = await Test();
            TimeSpan resultWithoutContent = await TestNoContext();

            //从执行结果很明显,不进行上下文同步的方式性能开销更少。
            this.textBox1.AppendText($"With the context:{resultWithContent}\n");
            this.textBox1.AppendText($"Without the context:{resultWithoutContent}\n");

        }

        static async Task<TimeSpan> Test()
        {
            const int iterationsNumber = 100000;
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < iterationsNumber; i++)
            {
                var t = Task.Run(() => { });
                //ConfigureAwait(true) 代码由同步执行进入异步执行时,当前同步执行的线程上下文信息会被
                //捕捉并保存到SynchronizaitonContext中,供异步执行中使用,在异步执行完成后(await之后的代码)
                //的同步执行中使用,在await之后虽然是同步执行,但是发生了线程切换,产生了开销,但此开销很小,几乎
                //不会影响性能问题

                //ConfigureAwait(flase)不进行线程上下文信息的捕捉,async方法中与await之后的代码执行时就无法获取await
                //之前的线程的上下文信息
                await t;
            }
            sw.Stop();
            return sw.Elapsed;
        }

        static async Task<TimeSpan> TestNoContext()
        {
            const int iterationsNumber = 100000;
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < iterationsNumber; i++)
            {
                var t = Task.Run(() => { });
                //ConfigureAwait(true) 代码由同步执行进入异步执行时,当前同步执行的线程上下文信息会被
                //捕捉并保存到SynchronizaitonContext中,供异步执行中使用,在异步执行完成后(await之后的代码)
                //的同步执行中使用,在await之后虽然是同步执行,但是发生了线程切换,产生了开销,但此开销很小,几乎
                //不会影响性能问题

                //ConfigureAwait(flase)不进行线程上下文信息的捕捉,async方法中与await之后的代码执行时就无法获取await
                //之前的线程的上下文信息
                await t.ConfigureAwait(continueOnCapturedContext: false);
            }
            sw.Stop();
            return sw.Elapsed;
        }
    }
}

5.7 对动态类型使用await

使用NuGet包安装ImpromptuInterface 一个轻量级的C#动态框架
ImpromptuInterface轻松将动态对象和接口绑定起来,从而直接利用接口中提供的属性和方法。
使用示例程序:

namespace ImpromptuExample {
    class Program {
        static void Main(string[] args) {
            dynamic expando = new ExpandoObject();
            expando.Name = "Albert";
            expando.Age = "24";
            expando.GetAge = (Func<int>)(() => { return 24; });
            expando.Prop1 = "WPF";

            //Improptu.ActLike动态实现继承接口的对象
            IMyInterface myInterface = Impromptu.ActLike(expando);

            Console.WriteLine(expando.Age);
            Console.WriteLine(expando.GetAge);
            Console.WriteLine(myInterface.Prop1);
            Console.ReadLine();
        }
    }

    public interface IMyInterface {
        string Prop1 { get; }
        long Prop2 { get; }
        Guid Prop3 { get; }
        bool Meth1(int x);
    }
}