Сводка по главе 20. Асинхронные и файловые операции ввода-вывода

Download Sample Скачайте пример

Примечание.

Эта книга была опубликована весной 2016 года и с тех пор не обновлялась. Многое в этой книге остается ценным, но некоторые материалы устарели, а некоторые разделы перестали быть полностью верными или полными.

Графический пользовательский интерфейс должен последовательно реагировать на события пользовательского ввода. Это означает, что вся обработка пользовательских событий должна выполняться в одном потоке, который часто называют основным потоком или потоком пользовательского интерфейса.

Пользователи ожидают, что графический интерфейс будет реагировать без задержек. Это означает, что программа должна быстро обрабатывать события пользовательского ввода. Если это невозможно, обработку следует передать на выполнение во вторичные потоки.

В нескольких примерах программ в этой книге уже использовался класс WebRequest. В этом классе метод BeginGetResponse запускает рабочий поток, который после завершения вызывает функцию обратного вызова. Но эта функция обратного вызова выполняется в рабочем потоке, а значит программа должна вызвать метод Device.BeginInvokeOnMainThread для доступа к пользовательскому интерфейсу.

Примечание.

Программы Xamarin.Forms для обращения к файлам через Интернет должны использовать HttpClient, а не WebRequest. HttpClient поддерживает асинхронные операции.

Более современный подход к асинхронной обработке доступен в .NET и C#. Сюда относятся классы Task и Task<TResult>, а также другие типы в пространствах имен System.Threading и System.Threading.Tasks и ключевые слова async и await из C# 5.0. Именно их мы рассматриваем в этой главе.

От обратных вызовов до ожидания

Сам класс Page содержит три асинхронных метода для отображения окон с предупреждениями:

Объекты Task указывают, что эти методы реализуют асинхронную модель на основе задач (TAP). Эти объекты Task быстро возвращаются методом. Возвращаемые значения Task<T> составляют так называемое обещание того, что после завершения задачи будет доступно значение типа TResult. Возвращаемое значение Task указывает на асинхронное действие, которое будет выполнено, но не возвращает значения.

Во всех этих случаях Task завершается, когда пользователь закрывает окно предупреждения.

Предупреждение с обратными вызовами

В примере AlertCallbacks показано, как управлять возвращаемыми объектами Task<bool> и вызовами Device.BeginInvokeOnMainThread с помощью методов обратного вызова.

Объявление с лямбда-выражениями

В примере AlertLambdas показано, как использовать анонимные лямбда-функции для обработки вызовов Task и Device.BeginInvokeOnMainThread.

Объявление с ожиданием

В рамках более прямого подхода используются ключевые слова async и await, которые появились в C# 5. Их использование демонстрируется в примере AlertAwait.

Оповещение без дополнительных элементов

Если асинхронный метод возвращает Task, а не Task<TResult>, программе не нужно использовать любые дополнительные методы, если не требуется знать время завершения асинхронной задачи. Этот подход демонстрируется в примере NothingAlert.

Асинхронное сохранение параметров программы

Пример SaveProgramChanges демонстрирует применение метода SavePropertiesAsync из Application для сохранения параметров программы при их изменении без переопределения метода OnSleep.

Таймер, независимый от платформы

С помощью Task.Delay вы можете создать независимый от платформы таймер. Этот подход демонстрируется в примере TaskDelayClock.

Файловый ввод-вывод

Традиционно в .NET пространство имен System.IO использовалось для поддержки файлового ввода-вывода. Некоторые методы в этом пространстве имен поддерживают асинхронные операции, но их немного. Пространство имен также поддерживает несколько простых вызовов методов, выполняющих сложные функции файлового ввода-вывода.

Что в этом хорошего и что плохого

Все платформы, поддерживаемые локальным хранилищем Xamarin.Forms приложения— хранилищем, частным для приложения.

Библиотеки Xamarin.iOS и Xamarin.Android включают версию .NET, которая специально адаптирована в Xamarin для этих двух платформ. Например, классы из System.IO можно использовать на этих двух платформах для выполнения файлового ввода-вывода в локальном хранилищем приложения.

Но вы не сможете найти эти классы System.IO в библиотеке PCL Xamarin.Forms. Проблема заключается в том, что корпорация Майкрософт полностью переработала механизм файлового ввода-вывода для API среды выполнения Windows. Программы, предназначенные для использования с Windows 8.1, Windows Phone 8.1 и универсальной платформы Windows, не используют System.IO для файлового ввода-вывода.

Это означает, что вам потребуется использовать DependencyService (сначала рассмотренный в главе 9. Вызовы API для конкретной платформы для реализации операций ввода-вывода файлов.

Примечание.

Переносимые библиотеки классов заменены библиотеками .NET Standard 2.0, а .NET Standard 2.0 поддерживает типы System.IO для всех платформ Xamarin.Forms. Вам больше не нужно использовать DependencyService для большинства задач файлового ввода-вывода. Более современный подход к операциям файлового ввода-вывода см. в главе Обработка файлов в Xamarin.Forms.

Подход к организации файлового ввода-вывода для нескольких платформ

Пример TextFileTryout определяет интерфейс IFileHelper для файлового ввода-вывода и содержит реализации этого интерфейса на всех платформах. Но реализации для среды выполнения Windows не работают с методами в этом интерфейсе, так как методы ввода-вывода в среде выполнения Windows являются асинхронными.

Адаптация к файловому вводу-выводу для среды выполнения Windows

Программы, которые работают в среде выполнения Windows, используют для файлового ввода-вывода классы из пространств имен Windows.Storage и Windows.Storage.Streams, в том числе для работы с локальным хранилищем приложения. Специалисты Майкрософт определили, что любая операция длительностью более 50 миллисекунд должна быть асинхронной, чтобы избежать блокировки в потоке пользовательского интерфейса, поэтому почти все эти методы файлового ввода-вывода являются асинхронными.

Код, демонстрирующий этот новый подход, будет размещен в библиотеке и доступен для других приложений.

Библиотеки для конкретных платформ

Многократно используемый код удобнее всего хранить в библиотеках. Конечно же, это становится намного сложнее, если разные фрагменты повторно используемого кода предназначены для совершенно разных операционных систем.

Один из возможных подходов демонстрируется в решении 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 содержит класс FileHelper для среды выполнения Windows.

Проект Xamarin.FormsBook.Platform.iOS содержит класс FileHelper для iOS. Эти методы теперь должны быть асинхронными. Некоторые из них используют асинхронные версии методов, которые определены в StreamWriter и StreamReader, то есть WriteAsync и ReadToEndAsync. Другие преобразуют результат в объект Task с помощью метода FromResult.

Проект Xamarin.FormsBook.Platform.Android содержит аналогичный класс FileHelper для Android.

Проект Xamarin.FormsBook.Platform также содержит класс FileHelper, который упрощает использование объекта DependencyService.

Чтобы использовать эти библиотеки, решение приложения должно включать все проекты в решении Xamarin.FormsBook.Platform, и каждый из проектов приложений должен иметь ссылку на соответствующую библиотеку из Xamarin.FormsBook.Platform.

В решении TextFileAsync демонстрируется использование библиотек Xamarin.FormsBook.Platform. Каждый из этих проектов вызывает Toolkit.Init. Приложение использует асинхронные функции файлового ввода-вывода.

Оставляем работать в фоновом режиме

Методы в библиотеках, которые вызывают несколько асинхронных методов, таких как WriteFileAsync методы и ReadFileASync методы в классе среда выполнения WindowsFileHelper, можно сделать несколько более эффективными с помощью ConfigureAwait метода, чтобы избежать переключения на поток пользовательского интерфейса.

Никогда не блокируйте поток пользовательского интерфейса.

Иногда есть соблазн обойтись без ContinueWith или await, просто указав для методов свойство Result. Но этого следует избегать, так как есть риск заблокировать поток пользовательского интерфейса, а также работу всего приложения.

Собственные методы для ожидания

Вы можете выполнить часть кода асинхронно, передав его в один из методов Task.Run. Task.Run можно вызвать в асинхронном методе, который берет на себя часть дополнительной работы.

Ниже обсуждаются некоторые подходы к использованию Task.Run.

Базовая реализация множества Мандельброта

Чтобы визуализировать множество Мандельброта в реальном времени, в библиотеке Xamarin.Forms.Toolkit предоставляется структура Complex, аналогичная структуре из пространства имен System.Numerics.

Пример MandelbrotSet содержит в файле кода программной части метод CalculateMandeblotAsync, который вычисляет базовое черно-белое отображение множества Мандельброта и с помощью BmpMaker размещает его в растровом изображении.

Отображение хода выполнения

Чтобы отслеживать ход выполнения асинхронного метода, вы можете создать экземпляр класса Progress<T> и определить в асинхронном методе аргумент с типом IProgress<T>. Этот подход демонстрируется в примере MandelbrotProgress.

Отмена задания

Можно также реализовать возможность отмены асинхронного метода. Для начала создайте класс с именем CancellationTokenSource. Свойство Token имеет значение с типом CancellationToken. Оно передается в асинхронную функцию. Программа вызывает метод Cancel из CancellationTokenSource (обычно в ответ на действие пользователя), чтобы отменить выполнение асинхронной функции.

Асинхронный метод может периодически проверять свойство IsCancellationRequested в CancellationToken и завершать работу, если это свойство имеет значение true, или просто вызвать метод ThrowIfCancellationRequested, чтобы завершить работу с вызовом исключения OperationCancelledException.

В примере MandelbrotCancellation показано использование функции с поддержкой отмены.

MVVM для множества Мандельброта

Пример MandelbrotXF предоставляет более сложный пользовательский интерфейс и почти полностью основан на классах MandelbrotModel и MandelbrotViewModel.

Triple screenshot of Mandelbrot X F

И опять о веб-решениях

Класс WebRequest, который используется в нескольких примерах, применяет старомодный протокол асинхронной работы APM. Вы можете перевести этот класс на использование более современного протокола TAP, применив любой из методов FromAsync класса TaskFactory. Пример ApmToTap демонстрирует такой сценарий.