Multithreading With C# Cookbook---Chapter5---使用C#6.0

时间:2021-11-13 21:02:02

概念

异步函数(asynchronous funcation)是TPL之上的更高级别的抽象,真正简化了异步编程。抽象隐藏了主要的实现细节,使得程序员无须考虑许多重要的事情,从而使异步编程更容易。

更多内容

创建异步函数,首先用async关键字标注一个方法(不能在Main中使用async),然后异步函数必须返回Task或Task<T>类型(不推荐使用async void方法)。在async关键字标注的方法内部,至少使用一个await操作符,否则会有编译警告。

在执行完await调用的代码行后该方法立即返回并将工作者线程放回线程池;如果是同步执行,执行线程将会阻塞两秒后返回结果。这允许在两秒或等待时间内将该工作者线程重用做其他事。这样可提高应用程序的可伸缩性。

借助异步函数,我们拥有了线性的程序控制流,但它的执行依然是异步的。如果程序连续出现两个await操作符,他们是顺序执行,先完成第一个第二个才会开始。

using

Multithreading With C# Cookbook---Chapter5---使用C#6.0Multithreading With C# Cookbook---Chapter5---使用C#6.0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Threading.Thread;
using System.Threading;

using System.Windows;
using System.Windows.Controls;
using System.Diagnostics;

using System.Runtime.CompilerServices;

using System.Dynamic;
using ImpromptuInterface;
View Code

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

定义了两个异步操作。第一个是标准TPL模式代码:启动一个任务,两秒后返回结果,定义一个后续操作来打印结果,再定义一个后续操作来捕获异常;第二个使用async和await:直接获取任务结果并打印,通过try……catch来捕获异常。

Multithreading With C# Cookbook---Chapter5---使用C#6.0Multithreading With C# Cookbook---Chapter5---使用C#6.0
class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronyWithTPL();
            t.Wait();

            t = AsynchronyWithAwait();
            t.Wait();

            Console.ReadLine();
        }
        static Task AsynchronyWithTPL()
        {

            Task<string> t = GetInfoAsync("Task1");
            //Console.WriteLine("123" + t.Result);
            Task t2 = t.ContinueWith(task => Console.WriteLine(t.Result), TaskContinuationOptions.NotOnFaulted);
            Task t3 = t.ContinueWith(task => Console.WriteLine(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted);

            return Task.WhenAny(t2, t3);


        }

        static async Task AsynchronyWithAwait()
        {
            try
            {
                string result = await GetInfoAsync("Task2");
                Console.WriteLine(result);
            }
            catch (Exception ex)
            {

                Console.WriteLine(ex.Message);
            }
        }
        static async Task<string> GetInfoAsync(string name)
        {
            await Task.Delay(TimeSpan.FromSeconds(2));
            //throw new Exception("1234");
            return $"{name} is running in the thread id:{CurrentThread.ManagedThreadId} thread. Is thread pool thread:{CurrentThread.IsThreadPoolThread} ";
        }
    }
View Code

两种模式在概念上是等同的,但第二种模式隐式处理了异步代码。

在lambda表达式中使用await关键字

使用lambda定义一个匿名方法,形参类型string,返回值类型Task<string>类型(这里返回的string类型,编译器自动产生一个Task并返回)。

Multithreading With C# Cookbook---Chapter5---使用C#6.0Multithreading With C# Cookbook---Chapter5---使用C#6.0
class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronousProcessing();
            t.Wait();

            Console.ReadLine();
        }

        static async Task AsynchronousProcessing()
        {
            Func<string, Task<string>> asyncLambda = async name =>//Func第一个参数是形参类型,第二个参数是返回类型
               {
                   await Task.Delay(TimeSpan.FromSeconds(2));
                   return $"{name} is running on a thread id {CurrentThread.ManagedThreadId}. Is thread pool thread:{CurrentThread.IsThreadPoolThread}";//返回的是string类型,编译器自动产生一个任务来返回一个Task<string>
               };
            string result = await asyncLambda("Async lambda");
            Console.WriteLine(result);
        }
    }
View Code

对连续的异步任务使用await操作符 

 两种模式的顺序执行:TPL顺序执行,使用Task.ContinueWith方法来指定下一步要执行的任务,并用该方法来打印结果与捕获异常;使用await与async,同样方法先执行到await代码行就会立即返回,剩下的代码将会在一个后续操作任务中运行。我们可以在Task.Wait方法之前,执行其他任务。

Multithreading With C# Cookbook---Chapter5---使用C#6.0Multithreading With C# Cookbook---Chapter5---使用C#6.0
class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronyWithTPL();
            t.Wait();
            t = AsynchronyWithAwait();
            Console.WriteLine("123");//这里会在任务被执行前先执行
            t.Wait();

            Console.ReadLine();
        }

        static async Task AsynchronyWithAwait()
        {
            try
            {
                //两个await任务会顺序执行,而不是并行
                string result = await GetInfoAsync("Await task 1");
                Console.WriteLine(result);
                result = await GetInfoAsync("Await task 2");
                Console.WriteLine(result);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message); ;
            }
        }

        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);//后续任务1:输出结果
                    t2.ContinueWith(innerTask => Console.WriteLine(innerTask.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent);
                }, TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent);//后续任务2:输出异常

                t.ContinueWith(task => Console.WriteLine(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent);
            });

            containerTask.Start();
            return containerTask;
        }

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

异步并不是总意味着并行执行。

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

先创建两个任务,然后用await Task.WhenAll方法来接收任务结果集。注意两种延时的区别。

Multithreading With C# Cookbook---Chapter5---使用C#6.0Multithreading With C# Cookbook---Chapter5---使用C#6.0
class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronousProcessing();
            t.Wait();

            Console.ReadLine();
        }

        static async Task AsynchronousProcessing()
        {
            Task<string> t1 = GetInfoAsync("Task1", 5);
            Task<string> t2 = GetInfoAsync("Task2", 3);
            string[] results = await Task.WhenAll(t1, t2);
            foreach (var v in results)
            {
                Console.WriteLine(v);
            }
        }

        static async Task<string> GetInfoAsync(string name, int seconds)
        {
            //await Task.Delay(TimeSpan.FromSeconds(seconds));//工作者线程执行到delay后,将下面代码块指定给线程后即返回线程池供其他任务使用
            await Task.Run(() => Thread.Sleep(TimeSpan.FromSeconds(seconds)));//工作者线程执行sleep后,将堵塞线程指定的时间,该期间其他任务不可使用
            Console.WriteLine($"{name} has done.");
            return $"Task {name} is running on a thread id:{CurrentThread.ManagedThreadId}. Is thread pool thread:{CurrentThread.IsThreadPoolThread}.";
            
        }
    }
View Code

处理异步操作中的异常

 捕获使用await情景中的几种异常:1,只有一个await任务;2,有多个await任务但只能捕获第一个任务的具体异常;3,有多个await任务异常,能捕获所有任务异常;4,演示C#6.0新特性:可以在catch…finally中使用await操作符。

Multithreading With C# Cookbook---Chapter5---使用C#6.0Multithreading With C# Cookbook---Chapter5---使用C#6.0
class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronousProcessing();
            t.Wait();

            Console.ReadLine();
        }

        static async Task AsynchronousProcessing()
        {
            Console.WriteLine("1.Single Exception");
            try
            {
                string result = await GetInfoAsync("1.Single Exception", 2);
                Console.WriteLine(result);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception details:{ex}");
            }
            Console.WriteLine();

            Console.WriteLine("2.Multiple Exceptions");
            Task<string> t1 = GetInfoAsync("2.Multiple Exceptions 1", 2);
            Task<string> t2 = GetInfoAsync("2.Multiple Exceptions 2", 3);
            try
            {
                string[] result = await Task.WhenAll(t1, t2);
                Console.WriteLine(result.Length);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception details:{ex}");
            }
            Console.WriteLine();

            Console.WriteLine("3.Mulitple exceptions with AggregateException");
            t1 = GetInfoAsync("3.Mulitple exceptions with AggregateException 1", 3);
            t2 = GetInfoAsync("3.Mulitple exceptions with AggregateException 2", 2);
            Task<string[]> t3 = Task.WhenAll(t1, t2);
            try
            {
                string[] results = await t3;
                Console.WriteLine(results.Length);
            }
            catch
            {
                var ae = t3.Exception.Flatten();
                var exceptions = ae.InnerExceptions;
                Console.WriteLine($"Exceptions caught:{exceptions.Count}");
                foreach (var item in exceptions)
                {
                    Console.WriteLine($"Exception details:{item}");
                    Console.WriteLine();
                }
            }
            Console.WriteLine();

            Console.WriteLine("4.Await in catch and finally blocks");
            try
            {
                string result = await GetInfoAsync("4.Await in catch and finally blocks", 2);
                Console.WriteLine(result);
            }
            catch (Exception ex)
            {
                await Task.Delay(TimeSpan.FromSeconds(2));
                Console.WriteLine($"Catch block with await: Exception details:{ex}");
            }
            finally
            {
                await Task.Delay(TimeSpan.FromSeconds(1));
                Console.WriteLine("Finally block");
            }

        }

        static async Task<string> GetInfoAsync(string name, int seconds)
        {
            await Task.Delay(TimeSpan.FromSeconds(seconds));//工作者线程执行到delay后,将下面代码块指定给线程后即返回线程池供其他任务使用
            throw new Exception("Boom from " + name);
        }
    }
View Code

避免使用捕获的同步上下文

一种使用常规的await操作符,一种使用带参数的ConfigureAwait方法,false参数明确指出不能对其使用捕获的同步上下文来运行后续操作代码。后一种方法花费时间短很多。

Multithreading With C# Cookbook---Chapter5---使用C#6.0Multithreading With C# Cookbook---Chapter5---使用C#6.0
class Program
    {
        private static Label _label;
        [STAThread]
        static void Main(string[] args)
        {
            var app = new Application();
            var win = new Window();
            var panel = new StackPanel();
            var button = new Button();
            _label = new Label();

            _label.FontSize = 32;
            _label.Height = 200;
            button.Height = 100;
            button.FontSize = 32;
            button.Content = new TextBlock { Text = "Start asynchronous operations" };
            button.Click += Click;
            panel.Children.Add(_label);
            panel.Children.Add(button);
            win.Content = panel;
            app.Run(win);

            Console.ReadLine();
        }

        static async void Click(object sender, EventArgs e)
        {
            _label.Content = new TextBlock { Text = "Calculating……" };
            TimeSpan resultWithContext = await Test();
            TimeSpan resultNoContext = await TestNoContext();
            //TimeSpan resultNoContext = await TestNoContext().ConfigureAwait(false);
            var sb = new StringBuilder();
            sb.AppendLine($"With the context:{resultWithContext}");
            sb.AppendLine($"Without the context:{resultNoContext}");
            sb.AppendLine($"Ratio:{resultWithContext.TotalMilliseconds / resultNoContext.TotalMilliseconds:0.00}");
            _label.Content = new TextBlock { Text = sb.ToString() };
        }

        static async Task<TimeSpan> Test()
        {
            const int interationNumber = 100000;
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < interationNumber; i++)
            {
                var t = Task.Run(() => { });
                await t;
            }
            sw.Stop();
            return sw.Elapsed;
        }

        static async Task<TimeSpan> TestNoContext()
        {
            const int interationNumber = 100000;
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < interationNumber; i++)
            {
                var t = Task.Run(() => { });
                await t.ConfigureAwait(continueOnCapturedContext: false);//尝试将延续任务封送回原始上下文,则为 true;否则为 false。
            }
            sw.Stop();
            return sw.Elapsed;
        }
    }
View Code

 

 

 

 

注:本文是在阅读《C#多线程编程实战》后所写,部分内容引用该书内容,这是一本不错的书,感谢!