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


    概念

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

    更多内容

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

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

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

    using

    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来捕获异常。

    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并返回)。

    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方法之前,执行其他任务。

    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方法来接收任务结果集。注意两种延时的区别。

    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操作符。

    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参数明确指出不能对其使用捕获的同步上下文来运行后续操作代码。后一种方法花费时间短很多。

    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#多线程编程实战》后所写,部分内容引用该书内容,这是一本不错的书,感谢! 

  • 相关阅读:
    现在, Delphi 的多线程已经非常易用了!
    发现 TSplitter 在嵌套时不好用, 索性写了个替代品
    关于显示透空歌词的思路 回复 "zhaoboaidelphi" 的问题
    简单获取钢琴 88 个键的音高频率值
    准备理一下菜单和工具栏相关的组件
    在 StringGrid 上画线时, 使用 GDI+ 以消除锯齿 回复 "gsjn_8888_6666" 的问题
    解压 svgz 到 svg
    jQuery能做到,PHP能做到,C#也能做到
    监测ASP.NET应用程序性能最简单的方法
    支持高并发的IIS Web服务器常用设置
  • 原文地址:https://www.cnblogs.com/EasonDongH/p/8526982.html
Copyright © 2020-2023  润新知