• C#并发编程——异步编程基础


      现代的异步.NET 程序使用两个关键字:async 和await。async 关键字加在方法声明上,它的主要目的是使方法内的await 关键字生效。如果async 方法有返回值,应返回Task<T>;如果没有返回值,应返回Task。这些task 类型相当于future,用来在异步方法结束时通知主程序。和其他方法一样,async 方法在开始时以同步方式执行。在async 方法内部,await 关键字对它的参数执行一个异步等待。它首先检查操作是否已经完成,如果完成了,就继续运行(同步方式)。否则,它会暂停async 方法,并返回,留下一个未完成的task。一段时间后,操作完成,async 方法就恢复运行。

      一个async 方法是由多个同步执行的程序块组成的,每个同步程序块之间由await 语句分隔。第一个同步程序块在调用这个方法的线程中运行,但其他同步程序块在哪里运行呢?情况比较复杂。
      最常见的情况是,用await 语句等待一个任务完成,当该方法在await 处暂停时,就可以捕捉上下文(context)。如果当前SynchronizationContext 不为空,这个上下文就是当前SynchronizationContext。如果当前SynchronizationContext 为空,则这个上下文为当前TaskScheduler。该方法会在这个上下文中继续运行。一般来说,运行UI 线程时采用UI 上下文,处理ASP.NET 请求时采用ASP.NET 请求上下文,其他很多情况下则采用线程池上下文。
      因此,在上面的代码中,每个同步程序块会试图在原始的上下文中恢复运行。如果在UI线程中调用DoSomethingAsync,这个方法的每个同步程序块都将在此UI 线程上运行。但是,如果在线程池线程中调用,每个同步程序块将在线程池线程上运行。
      要避免这种错误行为, 可以在await 中使用ConfigureAwait 方法, 将参数continueOnCapturedContext 设为false。接下来的代码刚开始会在调用的线程里运行,在被await 暂停后,则会在线程池线程里继续运行。

      不要用void 作为async 方法的返回类型! async 方法可以返回void,但是这仅限于编写事件处理程序。一个普通的async 方法如果没有返回值,要返回Task,而不是void。

      在编写任务并行程序时,要格外留意下闭包(closure)捕获的变量。记住闭包捕获的是引用(不是值),因此可以在结束时以不明显地方式分享这些变量。

      数据并行和任务并行都使用动态调整的分割器,把任务分割后分配给工作线程。线程池在需要的时候会增加线程数量。线程池线程使用工作窃取队列。

      每个.NET 程序都有一个线程池,线程池维护着一定数量的工作线程,这些线程等待着执行分配下来的任务。线程池可以随时监测线程的数量。

    1、暂停一段时间

       Task 类有一个返回Task 对象的静态函数Delay,这个Task 对象会在指定的时间后完成。

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace DemoTask
    {
        class Program
        {
            static void Main(string[] args)
            {
                TimeSpan t = new TimeSpan(0, 0, 1);
                DelayResult(100, t);
                Console.ReadLine();
            }
    
            static async Task<int> DelayResult(int result, TimeSpan delay)
            {
                await Task.Delay(delay);
                Console.WriteLine($"threadId={Thread.CurrentThread.ManagedThreadId}");
                return result;
            }
        }
    }
    View Code

    2、返回完成的任务

      可以使用Task.FromResult 方法创建并返回一个新的Task<T> 对象,这个Task 对象是已经完成的,并有指定的值。

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace DemoTask
    {
        class Program
        {
            static void Main(string[] args)
            {
                GetValueAsync();
                Console.ReadLine();
            }
            
            static Task<int> GetValueAsync()
            {
                return Task.FromResult(0);
            }
        }
    }
    View Code

    3、报告进度

      使用IProgress<T> 和Progress<T> 类型,编写的async 方法需要有IProgress<T> 参数。

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace DemoTask
    {
        class Program
        {
            static void Main(string[] args)
            {
                CallMethodAsync();
                Console.ReadLine();
            }
            
            static async Task MethodAsync(IProgress<double> progress)
            {
                double percent = 0;
                await Task.Run(() =>
                {
                    for(int i = 0; i < 10; i++)
                    {
                        percent = i;
                        progress.Report(percent);
                        Thread.Sleep(1000);
                    }
                });
            }
    
            static async Task CallMethodAsync()
            {
                var progress = new Progress<double>();
                progress.ProgressChanged += (sender, args) =>
                {
                    Console.WriteLine($"进度:{args}");
                };
                await MethodAsync(progress);
            }
        }
    }
    View Code

      需要注意的是,IProgress<T>.Report 方法可以是异步的。这意味着真正报告进度之前,MethodAsync 方法会继续运行。基于这个原因,最好把T 定义为一个不可变类型,或者至少是值类型。如果T 是一个可变的引用类型,就必须在每次调用IProgress<T>.Report 时,创建一个单独的副本。

    4、等待一组任务完成

      Task.WhenAll 方法可以实现这个功能。这个方法的输入为若干个任务,当所有任务都完成时,返回一个完成的Task 对象。

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace DemoTask
    {
        class Program
        {
            static void Main(string[] args)
            {
                FuncAsync();
                Console.ReadLine();
            }
            
            static async Task FuncAsync()
            {
                Task<int> task1 = Task.FromResult(1);
                Task<int> task2 = Task.FromResult(2);
                Task<int> task3 = Task.FromResult(3);
                int[] results = await Task.WhenAll(task1, task2, task3);
                foreach (var item in results)
                    Console.WriteLine(item);
            }
        }
    }
    View Code

      如果有一个任务抛出异常,则Task.WhenAll 会出错,并把这个异常放在返回的Task 中。如果多个任务抛出异常,则这些异常都会放在返回的Task 中。但是,如果这个Task 在被await 调用,就只会抛出其中的一个异常。如果要得到每个异常,可以检查Task.WhenALl返回的Task 的Exception 属性。

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace DemoTask
    {
        class Program
        {
            static void Main(string[] args)
            {
                ObserveOneExceptionAsync();
                ObserveAllExceptionsAsync();
                Console.ReadLine();
            }
            
            static async Task ThrowNotImplementedExceptionAsync()
            {
                await Task.FromResult(0);
                throw new NotImplementedException("NotImplementedException");
            }
    
            static async Task ThrowInvalidOperationExceptionAsync()
            {
                await Task.FromResult(0);
                throw new InvalidOperationException("InvalidOperationException");
            }
    
            static async Task ObserveOneExceptionAsync()
            {
                var task1 = ThrowNotImplementedExceptionAsync();
                var task2 = ThrowInvalidOperationExceptionAsync();
                try
                {
                    await Task.WhenAll(task1, task2);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
                Console.WriteLine("----------------");
            }
    
            static async Task ObserveAllExceptionsAsync()
            {
                var task1 = ThrowNotImplementedExceptionAsync();
                var task2 = ThrowInvalidOperationExceptionAsync();
                Task task = Task.WhenAll(task1, task2);
                try
                {
                    await task;
                }
                catch (Exception e)
                {
                    AggregateException ae = task.Exception;
                    foreach(var item in ae.InnerExceptions)
                        Console.WriteLine(item.Message);
                }
            }
        }
    }
    View Code

    5、等待任意一个任务完成

       使用Task.WhenAny 方法。该方法的参数是一批任务,当其中任意一个任务完成时就会返回。作为返回值的Task 对象,就是那个完成的任务。

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace DemoTask
    {
        class Program
        {
            static void Main(string[] args)
            {
                RunAsync();
                Console.ReadLine();
            }
            
            static Task<int> Read()
            {
                return Task.FromResult(1);
            }
    
            static Task<int> Write()
            {
                return Task.FromResult(2);
            }
    
            static async Task RunAsync()
            {
                var t1 = Read();
                var t2 = Write();
                Task<int> task = await Task.WhenAny(t1, t2);
                Console.WriteLine(task.Result);
            }
        }
    }
    View Code

      Task.WhenAny 返回的task 对象永远不会以“故障”或“已取消”状态作为结束。该方法的运行结果总是一个Task 首先完成。如果这个任务完成时有异常,这个异常也不会传递给Task.WhenAny 返回的Task 对象。

      第一个任务完成后,考虑是否要取消剩下的任务。如果其他任务没有被取消,也没有被继续await,那它们就处于被遗弃的状态。被遗弃的任务会继续运行直到完成,它们的结果会被忽略,抛出的任何异常也会被忽略。

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace DemoTask
    {
        class Program
        {
            static void Main(string[] args)
            {
                ObserveOneExceptionAsync();
                Console.ReadLine();
            }
    
            static async Task ThrowNotImplementedExceptionAsync()
            {
                await Task.FromResult(1);
                throw new NotImplementedException("NotImplementedException");
            }
    
            static async Task ThrowInvalidOperationExceptionAsync()
            {
                await Task.FromResult(2);
                throw new InvalidOperationException("InvalidOperationException");
            }
    
            static async Task ObserveOneExceptionAsync()
            {
                var task1 = ThrowNotImplementedExceptionAsync();
                var task2 = ThrowInvalidOperationExceptionAsync();
                try
                {
                    await Task.WhenAny(task1, task2);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
                Console.WriteLine("没返回异常");
            }
        }
    }
    View Code

    6、任务完成时的处理

      正在await 一批任务,希望在每个任务完成时对它做一些处理。另外,希望在任务一完成就立即进行处理,而不需要等待其他任务。

      最简单的方案是通过引入更高级的async 方法来await 任务,并对结果进行处理,从而重新构建代码。

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace DemoTask
    {
        class Program
        {
            static void Main(string[] args)
            {
                ProcessTasksAsync();
                Console.ReadLine();
            }
    
            static async Task<int> DelayAsync(int val)
            {
                await Task.Delay(TimeSpan.FromSeconds(val));
                return val;
            }
    
            static async Task AwaitAndProcessAsync(Task<int> t)
            {
                var result = await t;
                Console.WriteLine(result + "  " + DateTime.Now.Ticks / 1000 / 10000);
            }
    
            static async Task ProcessTasksAsync()
            {
                Task<int> t1 = DelayAsync(2);
                Task<int> t2 = DelayAsync(3);
                Task<int> t3 = DelayAsync(1);
                var tasks = new[] { t1, t2, t3 };
                Task[] ts = new Task[3];
                for (int i = 0; i < 3; i++)
                    ts[i] = AwaitAndProcessAsync(tasks[i]);
                await Task.WhenAll(ts);
            }
        }
    }
    View Code

    7、避免上下文延续

      在默认情况下,一个async 方法在被await 调用后恢复运行时,会在原来的上下文中运行。如果是UI 上下文,并且有大量的async 方法在UI 上下文中恢复,就会引起性能上的问题。

      为了避免在上下文中恢复运行,可让await 调用ConfigureAwait 方法的返回值,参数continueOnCapturedContext 设为false。

    8、处理async Task方法的异常

    9、处理async void方法的异常

  • 相关阅读:
    设计模式的分类
    SQL Server 2005 TSQL 学习笔记:排名函数
    JS正则表达式语法
    魅族 “拜产品教”公司的优秀与局限
    jqueryMobile初始化组件
    Loading Scripts Without Blocking
    LabJs学习笔记:分析图
    翻转的css3样式
    IE(IE6/IE7/IE8)支持HTML5标签
    我的空间轨迹!
  • 原文地址:https://www.cnblogs.com/ACGame/p/11032211.html
Copyright © 2020-2023  润新知