C#5.0作为第五个C#的重要版本,将异步编程的易用度推向一个新的高峰。通过新增的async和await关键字,几乎可以使用编写同步代码的方式来编写异步代码。

本文将重点介绍下新版C#的异步特性以及部分其他方面的改进。同时也将介绍WinRT程序一些异步编程的内容。

详情见:http://www.cnblogs.com/lsxqw2004/p/4922374.html 

C# async/await异步编程

写async异步编程这部分内容之前看了好多文章,反复整理自己的思路,尽力保证文章的正确性。尽管如此仍然可能存在错误,请广大园友及时指出,感谢感谢。

异步编程不是一个新鲜的话题,最早期的C#版本也内建对异步编程的支持,当然在颜值上无法与目前基于TAP,使用async/await的异步编程相比。异步编程要解决的问题就是许多耗时的IO可能会阻塞线程导致CPU空转降低效率,或者一个长时间的后台任务会阻塞用户界面。通过将耗时任务异步执行来使系统有更高的吞吐量,或保持界面的响应能力。如界面在加载一幅来自网络的图像时,还运行用户进行其他操作。

按前文惯例先上一张图通览一下TAP模式下异步编程的方方面面,然后由异步编程的发展来讨论一下TAP异步模式。

图1

APM

C# .NET最早出现的异步编程模式被称为APM(Asynchronous Programming Model)。这种模式主要由一对Begin/End开头的组成。BeginXXX方法用于启动一个耗时操作(需要异步执行的代码段),相应的调用EndXXX来结束BeginXXX方法开启的异步操作。BeginXXX方法和EndXXX方法之间的信息通过一个IAsyncResult对象来传递。这个对象是BeginXXX方法的返回值。如果直接调用EndXXX方法,则将以阻塞的方式去等待异步操作完成。另一种更好的方法是在BeginXXX倒数第二个参数指定的回调函数中调用EndXXX方法,这个回调函数将在异步操作完成时被触发,回调函数的第二个参数即EndXXX方法所需要的IAsyncResult对象。

.NET中一个典型的例子如System.Net命名空间中的HttpWebRequest类里的BeginGetResponse和EndGetResponse这对方法:

1
2
IAsyncResult BeginGetResponse(AsyncCallback callback, object state)
WebResponse EndGetResponse(IAsyncResult asyncResult)

由方法声明即可看出,它们符合前述的模式。

APM使用简单明了,虽然代码量稍多,但也在合理范围之内。APM两个最大的缺点是不支持进度报告以及不能方便的“取消”。

 

EAP

在C# .NET第二个版本中,增加了一种新的异步编程模型EAP(Event-based Asynchronous Pattern),EAP模式的异步代码中,典型特征是一个Async结尾的方法和Completed结尾的事件。XXXCompleted事件将在异步处理完成时被触发,在事件的处理函数中可以操作异步方法的结果。往往在EAP代码中还会存在名为CancelAsync的方法用来取消异步操作,以及一个ProgressChenged结尾的事件用来汇报操作进度。通过这种方式支持取消和进度汇报也是EAP比APM更有优势的地方。通过后文TAP的介绍,你会发现EAP中取消机制没有可延续性,并且不是很通用。

.NET2.0中新增的BackgroundWorker可以看作EAP模式的一个例子。另一个使用EAP的例子是被HttpClient所取代的WebClient类(新代码应该使用HttpClient而不是WebClient)。WebClient类中通过DownloadStringAsync方法开启一个异步任务,并有DownloadStringCompleted事件供设置回调函数,还能通过CancelAsync方法取消异步任务。

 

TAP & async/await

从.NET4.0开始新增了一个名为TPL的库主要负责异步和并行操作的处理,目标就是使异步和并发操作有个统一的操作界面。TPL库的核心是Task类,有了Task几乎不用像之前版本的异步和并发那样去和Thread等底层类打交道,作为使用者的我们只需要处理好Task,Task背后有一个名为的TaskScheduler的类来处理Task在Thread上的执行。可以这样说TaskScheduler和Task就是.NET4.0中异步和并发操作的基础,也是我们写代码时不二的选择。

对于Task可以将其理解为一个包装委托对象(通常就是Action或Func对象)并执行的容器,从Task对象的创建就可以看出:

1
2
3
4
5
Action action = () => Console.WriteLine("Hello World");
Task task1 = new Task(action);
 
Func<objectstring> func = name => "Hello World" + name;
Task<string> task2 = new Task<string>(func, "hystar" , CancellationToken.None,TaskCreationOptions.None );//接收object参数真蛋疼,很不容易区分重载,把参数都写上吧。

执行这个Task对象需要手动调用Start方法:

1
task1.Start();

这样task对象将在默认的TaskScheduler调度下去执行,TaskScheduler使用线程池中的线程,至于是新建还是使用已有线程这个对用户是完全透明的。还也可以通过重载函数的参数传入自定义的TaskScheduler。

关于TaskScheduler的调度,推荐园子里这篇文章,前半部分介绍了一些线程执行机制,很值得一度。

当我们用new创建一个Task对象时,创建的对象是Created状态,调用Start方法后将变为WaitingToRun状态。至于什么时候开始执行(进入Running状态,由TaskScheduler控制,)。Task的创建执行还有一种“快捷方式”,即Run方法:

1
2
Task.Run(() => Console.WriteLine("Hello World"));
var txt = await Task<string>.Run(() => "Hello World");

这种方式创建的Task会直接进入WaitingToRun状态。

Task的其他状态还有RanToCompletion,Canceled以及Faulted。在到大RanToCompletion状态时就可以获得Task<T>类型任务的结果。如果Task在状态为Canceled的情况下结束,会抛出 OperationCanceledException。如果以Faulted状态结束,会抛出导致任务失败的异常。

Task同时服务于并发编程和异步编程(在Jeffrey Richter的CLR via C#中分别称这两种模式为计算限制的异步操作和IO限制的异步操作,仔细想想这称呼也很贴切),这里主要讨论下Task和异步编程的相关的机制。其中最关键的一点就是Task是一个awaitable对象,这是其可以用于异步编程的基础。除了Task,还有很多类型也是awaitable的,如ConfigureAwait方法返回的ConfiguredTaskAwaitable、WinRT平台中的IAsyncInfo(这个后文有详细说明)等。要成为一个awaitable类型需要符合哪些条件呢?其实就一点,其中有一个GetAwaiter()方法,该方法返回一个awaiter。那什么是awaiter对象呢?满足如下3点条件即可:

  • 实现INotifyCompletion或ICriticalNotifyCompletion接口

  • 有bool类型的IsCompleted属性

  • 有一个GetResult()来返回结果,或是返回void

awaitable和awaiter的关系正如IEnumerable和IEnumerator的关系一样。推而广之,下面要介绍的async/await的幕后实现方式和处理yield语法糖的实现方式差不多。

Task类型的GetAwaiter()返回的awaiter是TaskAwaiter类型。这个TaskAwaiter很简单基本上就是刚刚满足上面介绍的awaiter的基本要求。类似于EAP,当异步操作执行完毕后,将通过OnCompleted参数设置的回调继续向下执行,并可以由GetResult获取执行结果。

 

简要了解过Task,再来看一下本节的重点 - async异步方法。async/await模式的异步也出来很久了,相关文章一大片,这里介绍下重点介绍下一些不容易理解和值得重点关注的点。我相信我曾经碰到的困惑也是很多人的遇到的困惑,写出来和大家共同探讨。

语法糖

对async/await有了解的朋友都知道这两个关键字最终会被编译为.NET中和异步相关的状态机的代码。这一部分来具体看一下这些代码,了解它们后我们可以更准确的去使用async/await同时也能理解这种模式下异常和取消是怎样完成的。

先来展示下用于分析反编译代码的例子,一个控制台项目的代码,这是能想到的展示异步方法最简单的例子了,而且和实际项目中常用的代码结构也差不太多:


C#6.0新特性的总结

参考PDF文档http://files.cnblogs.com/aehyok/VS2015CSharp6.0.pdf

1、自动属性的增强

1.1、自动属性初始化 (Initializers for auto-properties)

C#4.0下的果断实现不了的。

C#6.0中自动属性的初始化方式

只要接触过C#的肯定都会喜欢这种方式。真是简洁方便呀。

 

 1.2、只读属性初始化Getter-only auto-properties

先来看一下我们之前使用的方式吧

    public class Customer
    {
        public string Name { get; }

        public Customer(string firstName,string lastName)
        {
            Name = firstName +" "+ lastName;
        }
    }

再来看一下C#6.0中

    public class Customer
    {
        public string FirstName { get; }="aehyok";
        public string LastName { get; }="Kris";

    }

和第一条自动属性初始化使用方式一致。

2、Expression bodied function members

2.1 用Lambda作为函数体Expression bodies on method-like members

public Point Move(int dx, int dy) => new Point(x + dx, y + dy);  

再来举一个简单的例子:一个没有返回值的函数

public void Print() => Console.WriteLine(FirstName + " " + LastName);

 

2.2、Lambda表达式用作属性Expression bodies on property-like function members

        public override string ToString()
        { return FirstName + " " + LastName;
        }

现在C#6中

    public class User
    { public string FirstName { get; set; } public string LastName { get; set; } public override string ToString() => string.Format("{0}——{1}", FirstName, LastName); public string FullName => FirstName + " " + LastName;
    }

 

3、引用静态类Using Static

 在Using中可以指定一个静态类,然后可以在随后的代码中直接使用静态的成员

 

4、空值判断Null-conditional operators

 直接来看代码和运行结果

 通过结果可以发现返回的都为null,再也不像以前那样繁琐的判断null勒。

 

5、字符串嵌入值    

在字符串中嵌入值

之前一直使用的方式是

现在我们可以简单的通过如下的方式进行拼接

6、nameof表达式nameof expressions

 在方法参数检查时,你可能经常看到这样的代码(之前用的少,这次也算学到了)

        public static void AddCustomer(Customer customer)
        { if (customer == null)
            { throw new ArgumentNullException("customer");
            }
        }

里面有那个customer是我们手写的字符串,在给customer改名时,很容易把下面的那个字符串忘掉,C#6.0 nameof帮我们解决了这个问题,看看新写法

        public static void AddCustomer(Customer customer)
        { if (customer == null)
            { throw new ArgumentNullException(nameof(customer));
            }
        }

 

7、带索引的对象初始化器Index initializers 

 直接通过索引进行对象的初始化,原来真的可以实现

通过这种方式可以发现字典中只有三个元素,所以也就只有这三个索引可以访问额,其他类型的对象和集合也是可以通过这种方式进行初始化的,在此就不进行一一列举了。

8、异常过滤器 (Exception filters)

先来看一个移植过来的方法

            try { var numbers = new Dictionary<int, string> {[7] = "seven",[9] = "nine",[13] = "thirteen" };
            } catch (ArgumentNullException e)
            { if (e.ParamName == "customer")
                {
                    Console.WriteLine("customer can not be null");
                }
            }

在微软的文档中还给出了另一种用法,这个异常会在日志记录失败时抛给上一层调用者

        private static bool Log(Exception e)
        { ///处理一些日志 return false;
        } static void Main(string[] args)
        { try { ///  } catch (Exception e){if (!Log(e))
                {

                }
            }

            Console.ReadLine();
        }

 

9、catch和finally 中的 await —— Await in catch and finally blocks

 在C#5.0中,await关键字是不能出现在catch和finnaly块中的。而在6.0中

            try {
                res = await Resource.OpenAsync(…); // You could do this. …   } catch (ResourceException e)
            { await Resource.LogAsync(res, e); // Now you can do this …  } finally { if (res != null) await res.CloseAsync(); // … and this.  } 

 

10、无参数的结构体构造函数—— Parameterless constructors in structs

 

总结

参考:http://www.cnblogs.com/henryzhu/p/new-feature-in-csharp-6.html,总体感觉C#6.0加入的更多是语法糖(待自己去测试),语言本身没有太多新的概念,语法糖的加入会让C#程序员写出的代码更优美,更有生产力。



本文转载:CSDN博客