图形用户界面具有一点点特性,具有深远的影响:必须按顺序处理用户对应用程序的输入。无论用户输入事件是来自键盘,鼠标还是触摸,每个事件必须由应用程序完全处理 - 直接或通过用户界面对象(如按钮或滑块) - 在应用程序获得下一个用户之前 - 来自操作系统的输入事件。
经过一些反思后,这个限制背后的基本原理变得清晰,可能是一个例子:假设一个页面包含两个按钮,用户可以快速点击一个然后另一个按钮。这两个按钮可能会在两个独立的执行线程中同时处理这两个分接头吗?不,那不行。可能是第一个按钮改变了第二个按钮的含义,可能完全禁用它。因此,在第二个按钮开始处理自己的点击之前,必须允许第一个按钮完全处理其点击。
此限制的后果非常严重:必须在单个执行线程中处理对特定应用程序的所有用户输入。而且,用户界面对象通常不是线程安全的。无法从辅助执行线程修改它们。因此,与应用程序的用户界面连接的所有代码仅限于一个线程。此线程称为主线程或用户界面线程或UI线程。
几十年来,随着用户越来越习惯于图形用户界面,我们越来越不能容忍即使是最轻微的响应性失误。作为应用程序员,我们因此尽力保持用户界面响应以实现最大的用户满意度。这意味着在UI线程上运行的任何内容都必须尽快执行其处理并将控制权返回给操作系统。如果在UI线程中运行的事件处理程序在长处理作业中陷入困境,整个用户界面似乎会冻结并且肯定会使用户烦恼。
因此,应用程序必须执行的任何冗长的作业都应该降级为执行的辅助线程,通常称为工作线程。据说这些工作线程“在后台运行”并且不会干扰UI线程的响应性。
你已经在本书中看到了一些例子。几个示例程序 - 第13章中的ImageBrowser和BitmapStreams程序,“位图”,以及第19章“集合视图”中的SchoolOfFineArt库和RssFeed程序 - 使用WebRequest类通过Internet下载文件。对WebRequest的BeginGetResponse方法的调用启动了一个异步访问Web资源的工作线程。 WebRequest调用快速返回,程序可以在下载文件时处理其他用户输入。 BeginGetResponse的参数是在后台进程完成时调用的回调方法。在此回调方法中,程序调用EndGetResponse来访问下载的数据。
但传递给BeginGetResponse的回调方法有一点问题。回调方法在下载文件的同一工作线程中运行,在一般情况下,您无法从UI线程以外的任何其他方式访问用户界面对象。通常,这意味着回调方法必须访问UI线程。 Xamarin.Forms支持的三个平台中的每一个都有自己的本机方法,用于从UI线程上的辅助线程运行代码,但在Xamarin.Forms中,这些都可以通过Device.BeginInvokeOnMainThread方法获得。 (但是,您会记得,通常有一些与ViewModel相关的异常:虽然辅助线程无法直接访问用户界面对象,但辅助线程可以设置通过用户界面对象绑定的属性数据绑定。)
近年来,异步处理在程序员变得更容易的同时变得越来越普遍。这是一个持续的趋势:计算的未来无疑将涉及更多的异步计算和并行处理,特别是随着多核处理器芯片的使用越来越多。开发人员需要良好的操作系统支持和语言工具来处理异步操作,幸运的是.NET和C#一直处于这种支持的最前沿。
本章将探讨在Xamarin.Forms应用程序中使用异步处理的一些基础知识,包括使用.NET Task类来帮助您定义和使用异步方法。使用C#5.0中引入的两个关键字:async和await,大大减轻了处理回调函数的习惯麻烦。 await运算符通过简化异步调用的语法,澄清异步调用的程序流,通过简化用户界面对象的访问,简化工作线程引发的异常处理,以及统一处理异步调用,彻底改变了异步编程的语法。这些例外和取消后台工作。
本章主要演示如何使用异步处理来执行文件输入和输出,以及如何创建自己的工作线程以执行冗长的作业。
但是Xamarin.Forms本身包含几种异步方法。