第 20 章の概要: 非同期およびファイル I/O
注意
この本は 2016 年春に発行されて以降、改訂されていません。 多くの情報はまだ価値がありますが、一部の資料は古くなっており、トピックの中にはまったく正しくないものまたは不完全なものもあります。
グラフィカル ユーザー インターフェイスは、ユーザー入力イベントに順番に応答する必要があります。 これは、ユーザー入力イベントのすべての処理が、1 つのスレッド ("メイン スレッド" または "UI スレッド" と呼ばれることが多い) 内で発生する必要があることを示しています。
ユーザーは、グラフィカル ユーザー インターフェイスがレスポンシブであることを期待しています。 これは、プログラムで迅速にユーザー入力イベントを処理する必要があることを意味しています。 これが不可能な場合、処理は実行のセカンダリ スレッドに回される必要があります。
この書籍に含まれているいくつかのサンプル プログラムでは、WebRequest
クラスが使用されています。 このクラスでは、BeginGetResponse
メソッドによってワーカー スレッドが開始されます。これが完了すると、コールバック関数が呼び出されます。 ただし、そのコールバック関数はワーカー スレッドで実行されるため、プログラムではユーザー インターフェイスにアクセスするために、Device.BeginInvokeOnMainThread
メソッドを呼び出す必要があります。
注意
Xamarin.Forms プログラムでは、インターネット経由でファイルにアクセスするために、WebRequest
ではなく HttpClient
を使用する必要があります。 HttpClient
では非同期操作がサポートされています。
.NET および C# では、非同期処理に対するより新しいアプローチを使用できます。 そこでは、Task
および Task<TResult>
クラス、System.Threading
および System.Threading.Tasks
名前空間のその他の型、C# 5.0 の async
および await
キーワードが使用されます。 この章では、これに焦点を当てます。
Page
クラス自体に、アラート ボックスを表示するための 3 つの非同期メソッドが含まれています。
DisplayAlert
:Task
オブジェクトを返しますDisplayAlert
:Task<bool>
オブジェクトを返しますDisplayActionSheet
:Task<string>
オブジェクトを返します
Task
オブジェクトは、これらのメソッドでタスク ベースの非同期パターン (TAP と呼ばれます) が実装されていることを示します。 これらの Task
オブジェクトは、メソッドからすぐに返されます。 戻り値 Task<T>
は、タスクの完了時に TResult
型の値を使用できるようになる "約束" を構成します。 戻り値 Task
は非同期アクションを示します。これは完了しても、値は返されません。
これらのケースすべてにおいて、Task
は、ユーザーがアラート ボックスを閉じたときに完了します。
AlertCallbacks サンプルは、Task<bool>
の戻り値オブジェクトと Device.BeginInvokeOnMainThread
呼び出しを、コールバック メソッドを使用して処理する方法を示しています。
AlertLambdas サンプルは、Task
と Device.BeginInvokeOnMainThread
の呼び出しを処理するために、匿名のラムダ関数を使用する方法を示しています。
よりわかりやすい方法は、C# 5 で導入された async
および await
キーワードを使用するものです。 AlertAwait サンプルは、それらの使用方法を示しています。
非同期メソッドが Task<TResult>
ではなく Task
を返した場合、プログラムで非同期タスクがいつ完了するかを知る必要がなければ、これらの手法を使用する必要はありません。 NothingAlert サンプルはこのケースを示しています。
SaveProgramChanges サンプルは、Application
の SavePropertiesAsync
メソッドを使用して、OnSleep
メソッドをオーバーライドせずに、プログラムの設定が変更されたときにその設定を保存する方法を示しています。
Task.Delay
を使用して、プラットフォームに依存しないタイマーを作成することができます。 TaskDelayClock サンプルはこの方法を示しています。
従来は、.NET の System.IO
名前空間がファイル I/O のサポートのソースでした。 この名前空間の一部のメソッドでは非同期操作がサポートされていますが、ほとんどのメソッドではサポートされていません。 この名前空間では、高度なファイル I/O 関数を実行する、いくつかのシンプルなメソッド呼び出しもサポートされています。
Xamarin.Forms でサポートされているすべてのプラットフォームでは、アプリケーションのローカル ストレージ、すなわち、アプリケーション専用のストレージがサポートされています。
Xamarin.iOS と Xamarin.Android のライブラリには、これらの 2 つのプラットフォーム向けに Xamarin によって特別に調整されたバージョンの .NET が含まれています。 これらには System.IO
のクラスが含まれています。これらを使用すると、これら 2 つのプラットフォームでアプリケーションのローカル ストレージでのファイル I/O を実行できます。
しかし、Xamarin.Forms PCL でこれらの System.IO
クラスを検索しても、見つかりません。 問題は、Microsoft によって、Windows ランタイム API のファイル I/O が完全に改良されたことです。 Windows 8.1、Windows Phone 8.1、およびユニバーサル Windows プラットフォームをターゲットとするプログラムでは、ファイル I/O に System.IO
が使用されません。
つまり、DependencyService
を使用して (最初に「第 9 章: プラットフォーム固有の API 呼び出し」で説明されています)、ファイル I/O を実装する必要があります。
注意
ポータブル クラス ライブラリは .NET Standard 2.0 ライブラリに置き換えられています。 .NET Standard 2.0 では、すべての Xamarin.Forms プラットフォーム向けに System.IO
型がサポートされています。 ほとんどのファイル I/O タスクでは、DependencyService
を使用する必要がなくなりました。 ファイル I/O に対するより新しいアプローチについては、「Xamarin.Forms でのファイル処理」をご覧ください。
TextFileTryout サンプルでは、ファイル I/O 用の IFileHelper
インターフェイスと、すべてのプラットフォームでのこのインターフェイスの実装が定義されています。 しかし、Windows ランタイムの実装を、このインターフェイスのメソッドと連動させることはできません。Windows ランタイムのファイル I/O メソッドは非同期であるためです。
Windows ランタイムの下で実行されているプログラムでは、ファイル I/O 用に Windows.Storage
および Windows.Storage.Streams
名前空間のクラスが使用されます。これにはアプリケーションのローカル ストレージが含まれます。 UI スレッドのブロックを防止するために、Microsoft によって、50 ミリ秒を超える処理を必要とする操作はすべて非同期にすることが決められています。そのため、これらのファイル I/O メソッドはほとんどが非同期です。
この新しいアプローチを示すコードは、他のアプリケーションで使用できるようにライブラリに追加されます。
再利用可能なコードをライブラリに保存しておくと便利です。 これは当然、再利用可能なコードのさまざまな部分がまったく異なるオペレーティング システム用である場合、より困難になります。
Xamarin.FormsBook.Platform ソリューションは、1 つの方法を示しています。 このソリューションには、7 つの異なるプロジェクトが含まれています。
- Xamarin.FormsBook.Platform (通常の Xamarin.Forms PCL)
- Xamarin.FormsBook.Platform.iOS (iOS クラス ライブラリ)
- Xamarin.FormsBook.Platform.Android (Android クラス ライブラリ)
- Xamarin.FormsBook.Platform.UWP (ユニバーサル Windows クラス ライブラリ)
- Xamarin.FormsBook.Platform.WinRT (すべての Windows プラットフォームに共通するコード用の共有プロジェクト)
すべての個別のプラットフォーム用のプロジェクト (Xamarin.FormsBook.Platform.WinRT を除く) には、Xamarin.FormsBook.Platform への参照が含まれています。 3 つの Windows プロジェクトには、Xamarin.FormsBook.Platform.WinRT への参照が含まれています。
ライブラリが、Xamarin.Forms アプリケーション ソリューション内のプロジェクトによって直接参照されていない場合に読み込まれるように、すべてのプロジェクトには静的な Toolkit.Init
メソッドが含まれています。
Xamarin.FormsBook.Platform プロジェクトには、新しい IFileHelper
インターフェイスが含まれています。 すべてのメソッドの名前に Async
サフィックスが付けられ、Task
オブジェクトが返されるようになりました。
Xamarin.FormsBook.Platform.WinRT プロジェクトには、Windows ランタイム用の FileHelper
クラスが含まれています。
Xamarin.FormsBook.Platform.iOS プロジェクトには、iOS 用の FileHelper
クラスが含まれています。 これらのメソッドは非同期になっています。 一部のメソッドでは、StreamWriter
と StreamReader
で定義されているメソッドの非同期バージョン: WriteAsync
と ReadToEndAsync
が使用されています。 その他では、FromResult
メソッドを使用して、結果が Task
オブジェクトに変換されます。
Xamarin.FormsBook.Platform.Android プロジェクトには、Android 用の類似した FileHelper
クラスが含まれています。
Xamarin.FormsBook.Platform プロジェクトには、DependencyService
オブジェクトの使用を容易にする FileHelper
クラスも含まれています。
これらのライブラリを使用するには、アプリケーション ソリューションに Xamarin.FormsBook.Platform ソリューション内のすべてのプロジェクトを含め、また各アプリケーション プロジェクトに Xamarin.FormsBook.Platform 内の対応するライブラリへの参照を含める必要があります。
TextFileAsync ソリューションは、Xamarin.FormsBook.Platform ライブラリの使用方法を示しています。 各プロジェクトには Toolkit.Init
の呼び出しが含まれています。 アプリケーションでは、非同期ファイル I/O 関数が使用されます。
複数の非同期メソッドの呼び出しを行うライブラリ内のメソッド (Windows ランタイム FileHelper
クラスの WriteFileAsync
や ReadFileASync
メソッドなど) は、ConfigureAwait
メソッドを使用してユーザー インターフェイス スレッドへの切り替えを回避することで、多少効率を上げることができます。
場合によっては、メソッドの Result
プロパティを使用して、ContinueWith
や await
の使用を回避したくなることがあります。 これは、UI スレッドをブロックしたり、アプリケーションをハングさせたりする可能性があるため、行わないでください。
一部のコードを非同期に実行するには、それを Task.Run
メソッドのいずれかに渡します。 オーバーヘッドの一部を処理する非同期メソッド内で、Task.Run
を呼び出すことができます。
以下では、Task.Run
のさまざまなパターンについて説明します。
マンデルブロ集合をリアルタイムで描画するために、Xamarin.Forms.Toolkit ライブラリには、System.Numerics
名前空間に含まれているものと同様の Complex
構造が含まれています。
MandelbrotSet サンプルでは、分離コード ファイルに CalculateMandeblotAsync
メソッドが含まれています。これによって基本的な黒と白のマンデルブロ集合が計算され、BmpMaker
を使用してビットマップに配置されます。
非同期メソッドから進行状況を報告するには、Progress<T>
クラスのインスタンスを作成し、IProgress<T>
型の引数を持つように非同期メソッドを定義することができます。 これは MandelbrotProgress サンプルで示されています。
取り消し可能な非同期メソッドを作成することもできます。 まず CancellationTokenSource
という名前のクラスから開始します。 Token
プロパティは CancellationToken
型の値です。 これは、非同期関数に渡されます。 プログラムでは、非同期関数を取り消すために、(通常はユーザーのアクションに応答して) CancellationTokenSource
の Cancel
メソッドが呼び出されます。
非同期メソッドでは、CancellationToken
の IsCancellationRequested
プロパティを定期的にチェックし、プロパティが true
の場合に終了させることができます。または、単純に ThrowIfCancellationRequested
メソッドを呼び出すことができます。この場合、メソッドは OperationCancelledException
で終了します。
MandelbrotCancellation サンプルは、取り消し可能な関数の使用方法を示しています。
MandelbrotXF サンプルには、より豊富なユーザー インターフェイスが用意されています。これは、ほとんどの場合、MandelbrotModel
クラスと MandelbrotViewModel
クラスに基づいています。
一部のサンプルで使用されている WebRequest
クラスでは、非同期プログラミング モデルまたは APM と呼ばれる、旧式の非同期プロトコルが使用されています。 TaskFactory
クラスの FromAsync
メソッドのいずれかを使用して、このようなクラスを最新の TAP プロトコルに変換することができます。 ApmToTap サンプルでは、この方法が示されています。