C#线程5---await

简介:

前面异步编程的不足:它允 许我们以模块化的方式设计程序,来组合不同的异步操作。遗憾的是:

一: 当阅读此类程序时仍然非常难理解程序的实际执行顺序。在大型程序中将会有许多相互依赖的任务和后续操作,用于运行其他后续操作的后续操作,处理异常的后续操 作,并且它们都出现在程序代码中不同的地方。因此了解程序的先后执行次序变成了一个极具挑战性的问题。

二: 另一个需要关注的问题是,能够接触用户界面控制器的每个异步任务是否得到了正确的同步上下文。程序只允许通过UI线程使用这些控制器,否则将会得到多线程访问异常。说到异常,我们不得不使用单独的后续操作任务来处理在之前的异步操作中发生的错 误。这又导致了分散在代码的不同部分的复杂的处理错误的代码,逻辑上无法相互关联:

await的作用:为了解决这些问题,C# 5.0引入了新的语言特性,称为异步函数(asynchronous function)它是TPL之上的更高级别的抽象,真正简化了异步编程。正如在第4章提到的, 抽象隐藏了主要的实现细节,使得程序员无须考虑许多重要的事情,从而使异步编程更容 易。

三: async:要创建一个异步函数,首先需要用async关键字标注一个方法。如果不先做这个,就不可能拥有async属性或事件访问方法和构造函数。异步函数必须返回TaskTask<T>类型 可以使用async void 方法,但是更推荐使用async Task方法。使用async void方法唯一合理的地方是在程序中使用顶层UI控制器事件处理器的时候

1
2
3
4
5
async Task<string> GetStringAsync()
(
await Task.Delay(TimeSpan.FromSeconds(2));
return *'Hello/ World!n ;
}

四:使用async关键字标注的方法内部,可以使用await操作符。该操作符可与TPL的任务 一起工作,并获取该任务中异步操作的结果。在本章中稍后会讲述细节。在async方法外不 能使用await关键字,否则会有编译错误。另外,异步函数在其代码中至少要拥有一个await 操作符。然而,如果没有只会导致编译警告,而不是编译错误。

五:需要注意的是,在执行完await调用的代码行后该方法会立即返回。如果是同步执 行,执行线程将会阻塞两秒然后返回结果-这里当执行完await操作后,立即将工作者线程放回线程池的过程中,我们会异步等待。2秒后,我们又一次从线程池中得到工作者线 程并继续运行其中剩余的异步方法. 这允许我们在等待2秒时重用工作者线程做些其他 事,这对提高应用程序的可伸缩性非常重要:借助于异步函数我们拥有了线性的程序控制 流,但它的执行依然是异步的。这虽然好用,但是难以理解.如果程序中有两个连续的await操作符,此时程序如何工作有 —个常见的误解。很多人认为如果在另一个异步操作之后使用await函数,它们将会 并行运行。然而,事实上它们是顺序运行的,即第一个完成后第二个才会开始运行

六:注意事项:

a. 关联asyncawait有一定的限制°例如,在C# 5.0中,不能把控制台程序的Main方法 标记为async不能在catchfinally. lockunsafe代码块中使用await操作符。不允许对任 何异步函数使用refout参数。还有其他微妙的地方,但是以上已经包括了主要的需要注意 的地方。C#6.0去除了其中一些限制,由于编译器内部进行了改进,可以在catchfinally 代码块中使用await关键字.

b. 尽管 很多程序员几乎开始为每个方法使用async修饰符,我还是想强调如果方法本来无需异步 或并行运行,那么将该方法标注为async是没有道理的。调用async方法会有显著的性能 损失,通常的方法调用比使用async关键字的同样的方法调用要快上40〜50倍°请注意 这一点

 

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

复制代码
class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronyWithTPL();
            t.Wait();

            t = AsynchronyWithAwait();
            t.Wait();
            Read();
        }
        //当程序运行时运行了两个异步操作。其中一个是标准的TPL模式的代码,第二个使用了 C#的新特性async和awaiL AsynchronyWithTPL方法启动了一个任务,
        //运行两秒后返回关于工作者线程信息的字符串。然后我们定义了一个后续操作,用于在异步操作完成后打印出 该操作结果,
        //还有另一个后续操作,用于万一有错误发生时打印出异常的细节。最终,返回 了一个代表其中一个后续操作任务的任务,并等待其在Main函数中完成
        static Task AsynchronyWithTPL()
        {
            Task<string> t = GetInfoAsync("Task 1");
            Task t2 = t.ContinueWith(task => WriteLine(t.Result),
                TaskContinuationOptions.NotOnFaulted);
            Task t3 = t.ContinueWith(task => WriteLine(t.Exception.InnerException),
                TaskContinuationOptions.OnlyOnFaulted);

            return Task.WhenAny(t2, t3);
        }
        //在AsynchronyWithAwait方法中,我们对任务使用await并得到了相同的结果。这和编 写通常的同步代码的风格一样,即我们获取任务的结果,打印出结果,
        //如果任务完成时带有 错误则捕获异常。关键不同的是这实际上是一个异步程序。使用await后,C#立即创建了一 个任务,其有一个后续操作任务,
        //包含了 await操作符后面的所有剩余代码。这个新任务也 处理了异常传播。然后,将该任务返回到主方法中并等待其完成

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

        static async Task<string> GetInfoAsync(string name)
        {
            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}";
        }
    }
使用await获得结果
复制代码

2. lambda表达式中使用await操作符

复制代码
class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronousProcessing();
            t.Wait();
        }
        //首先,由于不能在Main方法中使用async,我们将异步函数移到了 Asynchronous- Processing 方法中°
        //然后使用async关键字声明了一个lambda表达式:由于任何lambda 表达式的类型都不能通过lambda自身来推断,
        //所以不得不显式向C#编译器指定它 的类型。在本例中,该类型说明该lambda表达式接受一个字符串参数,并返回一个 Task<string> 对象

        //我们定义了 lambda表达式体。有个问题是该方法被定义为返回一个Task<string> 对象,但实际上返回的是字符串,
        //却没有编译错误!这是因为C#编译器自动产生一个任务 并返回给我们。最后一步是等待异步lambda表达式执行并打印岀结果.
        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 thread: {CurrentThread.IsThreadPoolThread}";
            };

            string result = await asyncLambda("async lambda");

            WriteLine(result);
        }
    }
lambda表达式中使用await操作符
复制代码

3. 对连续的异步任务使用await操作符(当代码中有多个连续的await方法时程序的实际流程是怎样的)

复制代码
class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronyWithTPL();
            t.Wait();

            t = AsynchronyWithAwait();
            t.Wait();
        }
        /*AsynchronyWithTPL方法模仿了 AsynchronyWithAwait的程序流。我们需要一个容器任 务来处理所有相互依赖的任务。然后启动主任务,给其加了一组后续操作。
         * 当该任务完成 后,会打印出其结果。然后又启动了一个任务,在该任务完成后会依次运行更多的后续操 作。为了测试对异常的处理,
         * 当运行第二个任务时故意抛出一个异常,并打印出异常信息。 这组后续操作创建了与第一个方法中一样的程序流。如果用它与await方法比较,
         * 可以看到 它更容易阅读和理解。唯一的技巧是请记住异步并不总是意味着并行执行
         */
        static Task AsynchronyWithTPL()
        {
            var containerTask = new Task(() => { 
                Task<string> t = GetInfoAsync("TPL 1");
                t.ContinueWith(task => {
                    WriteLine(t.Result);
                    Task<string> t2 = GetInfoAsync("TPL 2");
                    t2.ContinueWith(innerTask => WriteLine(innerTask.Result),
                        TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent);
                    t2.ContinueWith(innerTask => WriteLine(innerTask.Exception.InnerException),
                        TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent);
                    },
                    TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent);

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

            containerTask.Start();
            return containerTask;
        }
        /*使用了两个await声明。最 重要的一点是该代码依然是顺序执行的,Async 2任务只有等之前的任务完成后才会开始执 行。
         * 当阅读该代码时,程序流很清晰,可以看到什么先运行,什么后运行。但该程序如何 是异步程序呢?首先,它不总是异步的。
         * 当使用await时如果一个任务已经完成,我们会异步地得到该任务结果。否则,当在代码中看到await声明时,
         * 通常的行为是方法执行到该await代码行时将立即返回,并且剩下的代码将会在一个后续操作任务中运行。
         * 因此等 待操作结果时并没有阻塞程序执行,这是一个异步调用。当AsynchronyWithAwait方法中 的代码在执行时,
         * 除了在Main方法中调用t.Wait外,我们可以执行任何其他任务。然而, 主线程必须等待直到所有异步操作完成,
         * 否则主线程完成后所有运行异步操作的后台线程 会停止运行
         */
        static async Task AsynchronyWithAwait()
        {
            try
            {
                string result = await GetInfoAsync("Async 1");
                WriteLine(result);
                result = await GetInfoAsync("Async 2");
                WriteLine(result);
            }
            catch (Exception ex)
            {
                WriteLine(ex);
            }
        }

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

 4. 对并行执行的异步任务使用await操作符(使用await来并行地运行异步任务,而不是采用常用的顺序执行)

复制代码
class Program
    {
        //这里定义了两个异步任务,分别运行3秒和5秒.然后使用Task.WhenAll辅助方法创 建了另一个任务,该任务只有在所有底层任务完成后才会运行」之后我们等待该组合任务的结果。
        //5秒后,我们获取了所有结果,说明了这些任务是同时运行的
        static void Main(string[] args)
        {
            Task t = AsynchronousProcessing();
            t.Wait();
        }
        /*这里定义了两个异步任务,分别运行3秒和5秒.然后使用Task.WhenAll辅助方法创 建了另一个任务,该任务只有在所有底层任务完成后才会运行」
         * 之后我们等待该组合任务的 结果。5秒后,我们获取了所有结果,说明了这些任务是同时运行的然而这里观察到一个有意思的现象。
         * 当运行该程序时.你可能注意到这两个任务似乎 是被线程池中的同一个工作者线程执行的。
         * 当我们并行运行任务时怎么可能发生这样的事情 呢?为了让事情更有趣,我们来注释掉GetlntroAsync方法中的await Task.Delay代码行,
         * 并 解除对await Task.Run代码行的注释,然后再次运行程序。我们会看到该情况下两个任务会被不同的工作者线程执行,
         * 不同之处是Task.Delay在 幕后使用了一个计时器,过程如下:从线程池中获取工作者线程,它将等待Task.Delay方法 返回结果。
         * 然后,Task.Delay方法启动计时器并指定一块代码,该代码会在计时器时间到了 Task.Delay方法中指定的秒数后被调用。
         * 之后立即将工作者线程返回到线程池中。当计时器 事件运行时,我们又从线程池中任意获取一个可用的工作者线程
         * (可能就是运行一个任务时 使用的线程)并运行计时器提供给它的代码。当使用Task.Run方法时,
         * 从线程池中获取了一个工作者线程并将其阻塞几秒,具体秒数 由Thread.Sleep方法提供:然后获取了第二个工作者线程并且也将其阻塞。
         * 在这种场景下,  我们消费了两个工作者线程,而它们绝对什么事没做,因为在它们等待时不能执行任何其他 操作。

         */
        static async Task AsynchronousProcessing()
        {
            Task<string> t1 = GetInfoAsync("Task 1", 3);
            Task<string> t2 = GetInfoAsync("Task 2", 5);

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

        static async Task<string> GetInfoAsync(string name, int seconds)
        {
            await Task.Delay(TimeSpan.FromSeconds(seconds));
            //await Task.Run(() => Thread.Sleep(TimeSpan.FromSeconds(seconds)));
            return
                $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}." +
                $" Is thread pool thread: {CurrentThread.IsThreadPoolThread}";
        }
    }
对连续的异步任务使用await
复制代码

5. 处理异步操作中的异常

复制代码
class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronousProcessing();
            t.Wait();
        }

        static async Task AsynchronousProcessing()
        {
            WriteLine("1. Single exception");
            //第一种情况是最简单的,并且与常见的同步代码几乎完全一样 我们只使用try/catch声明即 可获取异常细节
            try
            {
                string result = await GetInfoAsync("Task 1", 2);
                WriteLine(result);
            }
            catch (Exception ex)
            {
                WriteLine($"Exception details: {ex}");
            }

            WriteLine();
            WriteLine("2. Multiple exceptions");
            //一个很常见的错误是对一个以上的异步操作使用await时还使用以上方式.
            //像第一种情况一样使用catch代码块,则只能从底层的AggregateException对象中得到第一个 异常
            Task<string> t1 = GetInfoAsync("Task 1", 3);
            Task<string> t2 = GetInfoAsync("Task 2", 2);
            try
            {
                string[] results = await Task.WhenAll(t1, t2);
                WriteLine(results.Length);
            }
            catch (Exception ex)
            {
                WriteLine($"Exception details: {ex}");
            }

            WriteLine();
            WriteLine("3. Multiple exceptions with AggregateException");
            //为了收集所有异常信息,可以使用await任务的Exception属性,在第三种情况中,
            //我们使用AggregateException的Flatten方法将层级异常放入一个列表,并且从中提取岀所有的底 层异常。
            t1 = GetInfoAsync("Task 1", 3);
            t2 = GetInfoAsync("Task 2", 2);
            Task<string[]> t3 = Task.WhenAll(t1, t2);
            try
            {
                string[] results = await t3;
                WriteLine(results.Length);
            }
            catch
            {
                var ae = t3.Exception.Flatten();
                var exceptions = ae.InnerExceptions;
                WriteLine($"Exceptions caught: {exceptions.Count}");
                foreach (var e in exceptions)
                {
                    WriteLine($"Exception details: {e}");
                    WriteLine();
                }
            }

            WriteLine();
            WriteLine("4. await in catch and finally blocks");
            //为了演示C#6.0中的改变,我们在异常处理代码的tatch和finally代码块中使用了 await,
            //(在C#的早期版本中,在catch和finally代码块中是不可能使用await的)
            try
            {
                string result = await GetInfoAsync("Task 1", 2);
                WriteLine(result);
            }
            catch (Exception ex)
            {
                await Task.Delay(TimeSpan.FromSeconds(1));
                WriteLine($"Catch block with await: Exception details: {ex}");
            }
            finally
            {
                await Task.Delay(TimeSpan.FromSeconds(1));
                WriteLine("Finally block");
            }
            Read();
        }

        static async Task<string> GetInfoAsync(string name, int seconds)
        {
            await Task.Delay(TimeSpan.FromSeconds(seconds));
            throw new Exception($"Boom from {name}!");
        }
    }
await 处理异常
复制代码

6. 当使用await来获取异步操作结果时,同步上下文行为的细节

此功能需要加入对 Windows Presentation Foundation 库的引用

(1)右键点击项目中的引用文件夹,选择添加引用…菜单选项。

(2 )添加对 PresentationCore. PresentationFramework. System.Xaml 及 Windows.Base 库 的引用。你可以使用引用管理对话框的搜索功能

复制代码
//我们已经从第4章中了 解了任务调度程序和同步上下文。默认情况下,await操作符会尝试捕获同步上下文,并在其 中执行代码。
    //我们已经知道这有助于我们编写与用户界面控制器协作的异步代码。另外,使 用await不会发生在之前章节中描述过的死锁情况,
    //因为当等待结果时并不会阻塞UI线程。

    //在本例中,我们使用编程方式创建了 一个Windows Presentation Foundation应用程序并订阅了它的按钮点击事件。
    //当点击该按钮 时,运行了两个异步操作。其中一个使用了一个常规的await操作符,另一个使用了带false 参数值的ConfigureAwait
    //方法false参数明确指出我们不能对其使用捕获的同步上下文来运 行后续操作代码:在每个操作中,
    //我们测量了执行完成花费的时间,然后将各自的时间和比 例显示在主屏幕上.
    //结果看到常规的await操作符花费了更多的时间来完成。这是因为我们向UI线程中放 入了成百上千个后续操作任务,
    //这会使用它的消息循环来异步地执行这些任务。在本例中, 我们无需在UI线程中运行该代码,因为异步操作并未访问UI组件。
    //使用带false参数值的 ConfigureAwait方法是一个更高效的方案。
    //还有一件事值得一提。尝试运行程序并只点击按钮然后等待结果,然后再这样做一次,
    //但是这次点击按钮后尝试随机地拖拽应用程序窗口从一侧到另一侧。你将注意到在捕获的同 步上下文中的代码执行速度变慢了!
    //这个有趣的副作用完美演示了异步编程是多么危险。经 历类似的情况是非常容易的,而且如果你之前从未经历过这样的情况,
    //那么几乎不可能通过 调试来找出问题所在。
    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);

            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 iterationsNumber = 100000;
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < iterationsNumber; i++)
            {
                var t = Task.Run(() => { });
                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(() => { });
                await t.ConfigureAwait(
                    continueOnCapturedContext: false);
            }
            sw.Stop();
            return sw.Elapsed;
        }

    
    }
await 与界面的交互
复制代码

7. 使用async void(为什么使用async void方法非常危险)

强烈建议只在UI事件处理器中使用async void方法。在其他所有的情况下,请使用返 回Task的方法

复制代码
using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
using static System.Threading.Thread;

namespace Chapter5.Recipe7
{
    class Program
    {
        static void Main(string[] args)
        {
            //返回一个Task对象,通过返回的任务状态或对其调用 Wait方法从而很容易实现监控
            Task t = AsyncTask();
            t.Wait();

            //被声明为async void所以没有返回值,等待第二个方法完成的唯一方式是确切地等待多长时间,
            //因为我们没有声明任何对象可以监控该异步操作的状态。
            //当然可以使用某种共享的状态变 量,将其设置到async void方法中,并从调用方法中检查其值,
            //但返回一个Task对象的方 式更好些
            AsyncVoid();
            Sleep(TimeSpan.FromSeconds(3));

            t = AsyncTaskWithErrors();
            while(!t.IsFaulted)
            {
                Sleep(TimeSpan.FromSeconds(1));
            }
            //这里可以抓到异常
            WriteLine(t.Exception);

            //        try
            //        {
            ////这个任务是无返回的异步任务,使用async void方法,异常处理方法将被放置到当前的同步上下文中,
            ////在本例中即线程池中,线程池中未被处理的异常会终结整个进程(会导致程序崩溃)
            //    AsyncVoidWithErrors();
            //     Thread.Sleep(TimeSpan.FromSeconds(3));
            //        }
            //        catch (Exception ex)
            //        {
            //            Console.WriteLine(ex);//抓不到
            //        }

            //在lambda表达式中很容易忘记 对异常的处理.这将再次导致程序崩溃,加try也没用
            //try
            //{
                int[] numbers = { 1, 2, 3, 4, 5 };
                Array.ForEach(numbers, async number =>
                {
                    await Task.Delay(TimeSpan.FromSeconds(1));
                    if (number == 3) throw new Exception("Boom!");
                    WriteLine(number);

                });
            //}
            //catch (Exception ex)
   //         {
   //             Console.WriteLine(ex);
   //         }

            ReadLine();
        }

        static async Task AsyncTaskWithErrors()
        {
            string result = await GetInfoAsync("AsyncTaskException", 2);
            //因为发生了异常,下面语句不会运行,但可以从返回的结果中获取到异常 
            WriteLine("haha"+result);
        }

        static async void AsyncVoidWithErrors()
        {
            string result = await GetInfoAsync("AsyncVoidException", 2);
            WriteLine(result);
        }

        static async Task AsyncTask()

        {
            string result = await GetInfoAsync("AsyncTask", 2);
            WriteLine(result);
        }

        static async void AsyncVoid()
        {
            string result = await GetInfoAsync("AsyncVoid", 2);
            WriteLine(result);
        }

        static async Task<string> GetInfoAsync(string name, int seconds)
        {
            await Task.Delay(TimeSpan.FromSeconds(seconds));
            if(name.Contains("Exception"))
                throw new Exception($"Boom from {name}!");
            return
                $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}." +
                $" Is thread pool thread: {CurrentThread.IsThreadPoolThread}";
        }
    }
}
await async void慎用
复制代码

8. 设计一个自定义的awaitable类型

复制代码
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
using static System.Threading.Thread;

namespace Chapter5.Recipe8
{
    //如 果你安装了 Visual Studio 2015,那么可以在 C:\Program Files\Microsoft Visual Studio 14.0\VC#\ Specifications\1033
    //(假设你使用的是64位操作系统和默认安装路径)目录中找到该规格说明文档在规格说明文档的7.7.7.1节,我们发现了 awaitable表达式的定义

    /*如果一个表达式t满足下面任意一条则认为是 awaitable 的:
•    t是动态编译时的类型
•    t有一个名为GetAwaiter的可访问的实例或扩展方法。该方法没有参数和类型参数, 并且返回值类型A满足以下所有条件:
A 实现了 System. Runtime.CompilerServices. INotifyCompletion 接 口(为简单起见, 以后简称为 INotifyCompletion)o
A有一个可访问的、可读的类型为bool的实例属性IsCompletedo
A有一个名为GetResult的可访问的实例方法,该方法没有任何参數和类型参数,
这些信息足够我们开始了。首先我们定义一个awaitable类型CustomAwaitable,并实 现GetAwaiter方法,该方法返回一个CustomAwaiter类型的实例。
    CustomAwaiter实现了 INotifyCompletion接口、拥有类型为bool的IsCompleted属性,
    并且有GetResult方法,该 方法返回一个字符串类型。最后,我们写了一些代码来创建两个CustomAwaitable对象并对 其使用await关键字
     */
    class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronousProcessing();
            t.Wait();
            Read();
        }

        //基本上,如果IsCompleted属性返回true,则只需同步调用GetResult 方法°这种做法防止了该操作已经完成后我们仍然为执行异步任务而分配资源。
        //通过给 CustomAwaitable对象的构造函数传递completeSynchronousIy参数来展示该场景
        static async Task AsynchronousProcessing()
        {
            var sync = new CustomAwaitable(true);
            string result = await sync;
            WriteLine("true:"+result);

            var async = new CustomAwaitable(false);
            result = await async;

            WriteLine("false:"+result);
        }
        //另外,我们给CustomAwaiter的OnCompleted方法注册了一个回调函数并启动该异步操 作。当操作完成时,就会调用提供的回调函数,
        //该回调函数将会通过调用CustomAwaiter对 象的GetResult方法来获取结果。
        //该实现只适用于教学目的。当你编写异步函数时,最自然的方式还是使用标准的 Task类型.只有具有无法使用Task的坚实理由,
        //你才可以自定义awaitable类型,并 且你需要真的知道你在做什么
        class CustomAwaitable
        {
            public CustomAwaitable(bool completeSynchronously)
            {
                _completeSynchronously = completeSynchronously;
            }

            public CustomAwaiter GetAwaiter()
            {
                return new CustomAwaiter(_completeSynchronously);
            }

            private readonly bool _completeSynchronously;
        }

        class CustomAwaiter : INotifyCompletion
        {
            private string _result = "Completed synchronously";
            private readonly bool _completeSynchronously;

            public bool IsCompleted => _completeSynchronously;

            public CustomAwaiter(bool completeSynchronously)
            {
                _completeSynchronously = completeSynchronously;
            }

            public string GetResult()
            {
                return _result;//true就直接获取这里的结果
            }

            public void OnCompleted(Action continuation)
            {
                ThreadPool.QueueUserWorkItem( state => {
                    Sleep(TimeSpan.FromSeconds(1));
                    _result = GetInfo();//false才一直跑完
                   continuation?.Invoke();
                });
            }

            private string GetInfo()
            {
                return
                    $"Task is running on a thread id {CurrentThread.ManagedThreadId}." +
                    $" Is thread pool thread: {CurrentThread.IsThreadPoolThread}";
            }
        }
    }
}
自定义awaitable
复制代码

9. 对动态类型使用await

  1. 请执行以下步骤来添加对Impromptuinterface NuGet包的引用:

(1)右键点击项目中的引用文件夹,并选择管理NuGet包菜单选项。

(2 )添加对你喜欢的Impromptuinterface NuGet包的引用。可以使用管理NuGet包对 话框的搜索功能

复制代码
using System;
using System.Dynamic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using ImpromptuInterface;
using static System.Console;
using static System.Threading.Thread;

namespace Chapter5.Recipe9
{
    /*这里我们重复了 5.9节的技巧,但是这次借助于动态表达式,可以使用NuGet来实现该 目标。NuGet是一个包含了很多有用的库的包管理器。这次我们将使用一个库来动态地创建 封装对象,实现我们需要的接口。
首先我们创建了 ExpandoObject类型的两个实例,并把它们分配给动态的局部变量。这 些变量将成为awaitable和awaiter对象 由于一个awaitable对象只需要拥有GetAwaiter方 法,提供该方法没有问题。使用dynamic关键字组合ExpandoOjbect允许我们自定义该对象 并通过分配相应的值来添加属性和方法。事实上它是一个字典类型的集合,键类型是string, 值类型是object如果你很熟悉JavaScript编程语言,你可能会注意到它与JavaScript对象很 相似。
由于dynamic关键字允许我们跳过C#的编译时检查。ExpandObject是以这样的方式编 写的:当你给属性分配值时,ExpandObject创建了一个字典条目,键是属性名,值是赋予 的任何值。当尝试获取属性值时,会在字典中查找并提供存储在相应的字典条目中的值。如 果该值是Action或Func类型,我们实际上存储了一个委托,它可以当作方法使用。因此, ExpandoObject与dynamic类型的组合允许我们创建一个对象并动态地赋予其属性和方法。
现在我们需要构造自定义的awaiter和awaitablc对象先从awaiter开始。首先提供一 个名为Message的属性并赋予初始值,然后使用Func<string>类型定义了 GetResult方法, 并分配一个lambda表达式,该表达式返回Message属性值。接下来实现IsCompleted属性。 如果其值为true,则跳过剩下的工作并处理存储在result局部变量中的awaitable对象。我们 只需要添加一个方法用于返回该dynamic对象并从该对象返回awaiter对象 我们可以使用 result作为await表达式。然而,它将会同步运行。
主要的挑战是在动态对象中实现异步处理。C#语言规格说明规定awaiter必须实现 INotifyCompletion 或 ICriticalNotifyCompletion 接口 ,但是 ExpandoObject 却没有。甚至当我 们动态地实现OnCompleted方法并添加到awaiter对象时,这仍然行不通,因为该对象没有 实现上面提到的任何一个接口。
为了解决该问题,我们使用了 NuGet提供的Impromptuinterface库.它允许我们使用 Impromptu.ActLike方法来动态地创建代理对象,该对象将实现任何需要的接口。如果我们尝 试创建一个实现了 INotifyCompletion接口的代理,仍然行不通,因为该代理对象不再是动态 的,并且该接口只有OnCompleted方法,但没有IsCompleted属性或GetResult方法。作为最 后的解决办法,我们定义了一个泛型接口,lAwaiter<T>,它实现了 INotifyCompletion并添 加了所有需要的属性和方法。现在,我们使用它生成代理并修改result对象来从GetAwaiter 方法返回一个代理,而不是返回awaiter对象。现在程序可以工作了,我们构造了一个在运 行时完全动态的awaitable对象 
     */
    class Program
    {
        static void Main(string[] args)
        {
            Task t = AsynchronousProcessing();
            t.Wait();
        }

        static async Task AsynchronousProcessing()
        {
            string result = await GetDynamicAwaitableObject(true);
            WriteLine(result);

            result = await GetDynamicAwaitableObject(false);
            WriteLine(result);
        }

        static dynamic GetDynamicAwaitableObject(bool completeSynchronously)
        {
            dynamic result = new ExpandoObject();
            dynamic awaiter = new ExpandoObject();

            awaiter.Message = "Completed synchronously";
            awaiter.IsCompleted = completeSynchronously;
            awaiter.GetResult = (Func<string>)(() => awaiter.Message);

            awaiter.OnCompleted = (Action<Action>) ( callback => 
                ThreadPool.QueueUserWorkItem(state => {
                    Sleep(TimeSpan.FromSeconds(1));
                    awaiter.Message = GetInfo();
                    callback?.Invoke();
                })
            );

            IAwaiter<string> proxy = Impromptu.ActLike(awaiter);

            result.GetAwaiter = (Func<dynamic>) ( () => proxy );

            return result;
        }

        static string GetInfo()
        {
            return
                $"Task is running on a thread id {CurrentThread.ManagedThreadId}." +
                $" Is thread pool thread: {CurrentThread.IsThreadPoolThread}";
        }
    }

    public interface IAwaiter<T> : INotifyCompletion
    {
        bool IsCompleted { get; }

        T GetResult();
    }
}
View Code
复制代码

 

posted @   apple-hu  阅读(107)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示