摘要:第 20 章. 异步和文件 I/O

注意

本书于 2016 年春季出版,之后再未更新。 书中有许多内容仍然有价值,但有些内容已过时,有些主题不再完全正确或完整。

图形用户界面必须按顺序响应用户输入事件。 这意味着,用户输入事件的所有处理都必须发生在单个线程中,通常称为主线程或 UI 线程

用户希望图形用户界面能够快速响应。 这意味着程序必须快速处理用户输入事件。 如果不能,则必须将处理操作归入辅助执行线程。

本书中的几个示例程序已使用 WebRequest 类。 在此类中,BeginGetResponse 方法会启动一个工作线程,该线程在完成后将调用回调函数。 但是,该回调函数在工作线程中运行,因此,程序必须调用 Device.BeginInvokeOnMainThread 方法才能访问用户界面。

注意

Xamarin.Forms 程序应使用 HttpClient 而不是 WebRequest 来通过 Internet 访问文件。 HttpClient 支持异步操作。

.NET 和 C# 中提供了更新式的异步处理方法。 这涉及到 TaskTask<TResult> 类、System.ThreadingSystem.Threading.Tasks 命名空间中的其他类型以及 C# 5.0 asyncawait 关键字。 这就是本章重点介绍的内容。

从回调到等待

Page 类本身包含三种显示警报框的异步方法:

Task 对象指示这些方法实现基于任务的异步模式 (TAP)。 这些 Task 对象从方法快速返回。 Task<T> 返回值构成一个“承诺”,即 TResult 类型的值在任务完成后可用。 Task 返回值指示这是一个将完成但没有返回值的异步操作。

在所有这些情况下,当用户消除警报框时,Task 就会完成。

带有回调的警报

AlertCallbacks 示例演示如何使用回调方法处理 Task<bool> 返回对象和 Device.BeginInvokeOnMainThread 调用。

使用 lambda 的警报

AlertLambdas 示例演示如何使用匿名 lambda 函数来处理 TaskDevice.BeginInvokeOnMainThread 调用。

使用 await 的警报

这是更直接的方法,涉及 C# 5 中引入的 asyncawait 关键字。 AlertAwait 示例演示了其用法。

不包含任何内容的警报

如果异步方法返回 Task 而不是 Task<TResult>,则当程序无需知道异步任务何时完成时,就不需要使用任何这些方法。 NothingAlert 示例演示了这一点。

异步保存程序设置

SaveProgramChanges 示例演示如何使用 ApplicationSavePropertiesAsync 方法,在更改程序设置时对其进行保存,而不重写 OnSleep 方法。

独立于平台的计时器

可以使用 Task.Delay 来创建与平台无关的计时器。 TaskDelayClock 示例演示了这一点。

文件输入/输出

传统上,.NET System.IO 命名空间已是文件 I/O 支持的来源。 虽然此命名空间中的某些方法支持异步操作,但大多数情况下都不支持。 命名空间还支持多个执行复杂的文件 I/O 函数的简单方法调用。

好消息和坏消息

Xamarin.Forms 支持的所有平台都支持应用程序本地存储 – 专用于应用程序的存储。

Xamarin.iOS 和 Xamarin.Android 库包括 Xamarin 专门为这两个平台定制的 .NET 版本。 其中包括来自 System.IO 的类,这些类可用于在这两个平台中通过应用程序本地存储执行文件 I/O。

但是,如果在 Xamarin.Forms PCL 中搜索这些 System.IO 类,你将找不到它们。 问题在于,Microsoft 完全改变了 Windows 运行时 API 的文件 I/O。 面向 Windows 8.1、Windows Phone 8.1 和通用 Windows 平台的程序不会将 System.IO 用于文件 I/O。

这意味着,你将需要使用 DependencyService(最先在第 9 章:平台特定的 API 调用中进行讨论)来实现文件 I/O。

注意

可移植类库已替换为 .NET Standard 2.0 库,.NET Standard 2.0 支持所有 Xamarin.Forms 平台的 System.IO 类型。 不再需要对大多数文件 I/O 任务使用 DependencyService。 有关文件 I/O 的更新式方法,请参阅 Xamarin.Forms 中的文件处理

跨平台文件 I/O 的第一步

TextFileTryout 示例定义了用于文件 I/O 的 IFileHelper 接口,以及所有平台中此接口的实现。 但 Windows 运行时实现不能与此接口中的方法一起使用,因为 Windows 运行时文件 I/O 方法是异步的。

容纳 Windows 运行时文件 I/O

在 Windows 运行时下运行的程序将 Windows.StorageWindows.Storage.Streams 命名空间中的类用于文件 I/O(包括应用程序本地存储)。 由于 Microsoft 确定需要超过 50 毫秒的任何操作都应该是异步的,以避免阻止 UI 线程,因此,这些文件 I/O 方法大多都是异步的。

演示此新方法的代码将位于库中,以便其他应用程序可以使用该代码。

特定于平台的库

在库中存储可重用代码非常有利。 当可重用代码的不同部分适用于完全不同的操作系统时,这种情况明显更难。

Xamarin.FormsBook.Platform 解决方案演示了一种方法。 此解决方案包含七个不同的项目:

所有单个平台项目(除了 Xamarin.FormsBook.Platform.WinRT 之外)都具有对 Xamarin.FormsBook.Platform 的引用。 这三个 Windows 项目都具有对 Xamarin.FormsBook.Platform.WinRT 的引用。

所有项目都包含一个静态 Toolkit.Init 方法,以确保当 Xamarin.Forms 应用程序解决方案中的项目不直接引用库时进行加载。

Xamarin.FormsBook.Platform 项目中包含新的 IFileHelper 接口。 所有方法的名称现在都具有 Async 后缀,并返回 Task 对象。

Xamarin.FormsBook.Platform.WinRT 项目包含适用于 Windows 运行时的 FileHelper 类。

Xamarin.FormsBook.Platform.iOS 项目包含适用于 iOS 的 FileHelper 类。 这些方法现在必须是异步的。 某些方法使用在 StreamWriterStreamReader 中定义的异步方法:WriteAsyncReadToEndAsync。 其他方法使用 FromResult 方法将结果转换为 Task 对象。

Xamarin.FormsBook.Platform.Android 项目包含类似的适用于 Android 的 FileHelper 类。

Xamarin.FormsBook.Platform 项目还包含一个 FileHelper 类,以便轻松使用 DependencyService 对象。

若要使用这些库,应用程序解决方案必须包含 Xamarin.FormsBook.Platform 解决方案中的所有项目,并且每个应用程序项目必须具有对 Xamarin.FormsBook.Platform 中的相应库的引用。

TextFileAsync 解决方案演示了如何使用 Xamarin.FormsBook.Platform 库。 每个项目都具有对 Toolkit.Init 的调用。 应用程序使用异步文件 I/O 函数。

使其保持在后台

如果库中的方法调用多个异步方法(例如 Windows 运行时 FileHelper 类中的 WriteFileAsyncReadFileASync 方法),则可以通过使用 ConfigureAwait 方法来避免切回用户界面线程,从而在一定程度上提高效率。

不要阻止 UI 线程!

有时,通过使用方法上的 Result 属性,可以避免使用 ContinueWithawait。 应避免这种情况,因为它可能会阻止 UI 线程,甚至挂起应用程序。

你自己的可等待方法

你可以通过将代码传递到 Task.Run 方法之一来异步运行一些代码。 可以在处理一些开销的异步方法中调用 Task.Run

下面讨论了各种 Task.Run 模式。

基本 Mandelbrot 集

为了实时绘制 Mandelbrot 集,Xamarin.Forms.Toolkit 库中的 Complex 结构与 System.Numerics 命名空间中的结构类似

MandelbrotSet 示例在其代码隐藏文件中有一个 CalculateMandeblotAsync 方法,该方法计算基本的黑白 Mandelbrot 集,并使用 BmpMaker 将其放在位图上。

标记进度

若要从异步方法报告进度,可以实例化一个 Progress<T> 类,并定义异步方法,使其具有 IProgress<T> 类型的参数。 MandelbrotProgress 示例对此进行了演示。

取消作业

还可以编写可取消的异步方法。 首先,使用名为 CancellationTokenSource 的类。 Token 属性是 CancellationToken 类型的值。 这会传递到异步函数。 程序调用 CancellationTokenSourceCancel 方法(通常是为了响应用户的操作)以取消异步函数。

异步方法可以定期检查 CancellationTokenIsCancellationRequested 属性,如果该属性为 true,则退出;或者只是调用 ThrowIfCancellationRequested 方法,在这种情况下,方法以 OperationCancelledException 结尾。

MandelbrotCancellation 示例演示如何使用可取消函数。

MVVM Mandelbrot

MandelbrotXF 示例具有更广泛的用户界面,它主要基于 MandelbrotModelMandelbrotViewModel 类:

Mandelbrot X F 的三倍屏幕截图

返回到 Web

某些示例中采用的 WebRequest 类使用称为异步编程模型 (APM) 的老式异步协议。 你可以使用 TaskFactory 类中的 FromAsync 方法之一将此类转换为新式 TAP 协议。 ApmToTap 示例演示了这一点。