Глава.28 .Асинхронные.операции.ввода-вывода
будет недоступен в течение еще большего времени. Таким образом, если вы хотите
инициировать асинхронную функцию из другого потока, используйте статический
метод
Run
класса
Task
:
// Task.Run вызывается в потоке графического интерфейса
Task.Run(async () => {
// Этот код выполняется в потоке из пула
// TODO: Подготовительные вычисления...
await XxxAsync(); // Инициирование асинхронной операции
// Продолжение обработки...
});
В этом коде продемонстрирована еще одна полезная возможность C#: асинхронные
лямбда-выражения. Дело в том, что оператор
awat
нельзя просто поместить в тело
обычного лямбда-выражения, потому что компилятор не сможет преобразовать метод
в конечный автомат. Размещение
async
перед лямбда-выражением заставит компи-
лятор преобразовать лямбда-выражение в метод конечного автомата, возвращающий
значение
Task
или
Task
, которое может быть присвоено любой делегатной
переменной
Func
с типом возвращаемого значения
Task
или
Task
.
При написании кода очень легко вызвать функцию
async
, забыв об использо-
вании оператора
await
:
static async Task OuterAsyncFunction() {
InnerAsyncFunction(); // В этой строке пропущен оператор await!
// Код продолжает выполняться, как и InnerAsyncFunction...
}
static async Task InnerAsyncFunction() { /* ... */ }
К счастью, в таких ситуациях компилятор C# выдает предупреждение и пред-
лагает применить оператор
await
к результату вызова. Это хорошо, но в отдельных
случаях вас действительно не интересует, когда завершится
InnerAsyncFunction
,
и вы бы предпочли использовать этот код без предупреждений компилятора. Чтобы
избавиться от предупреждения, просто присвойте переменной объект
Task
, возвра-
щенный
InnerAsyncFunction
. В дальнейшем переменную можно игнорировать
1
.
static async Task OuterAsyncFunction() {
var noWarning = InnerAsyncFunction(); // Строка без await
// Этот код продолжает выполняться, как и код InnerAsyncFunction...
}
А я предпочитаю определять метод расширения, который выглядит так:
[MethodImpl(MethodImplOptions.AggressiveInlining)] // Заставляет компилятор
// убрать вызов при оптимизации
public static void NoWarning(this Task task) { /* Не содержит кода */ }
1
К счастью, компилятор не выдает предупреждения о том, что локальная переменная не
используется в программе.
809
Другие.возможности.асинхронных.функций
Далее метод используется следующим образом:
static async Task OuterAsyncFunction() {
InnerAsyncFunction().NoWarning(); // Строка без await
// Код продолжает выполняться, как и InnerAsyncFunction...
}
У асинхронных операций ввода-вывода есть одна действительно замечательная
особенность: вы можете инициировать сразу несколько операций, чтобы они вы-
полнялись параллельно. Это может обеспечить феноменальный прирост произво-
дительности приложения. Я еще нигде не приводил код, который запускает мой
сервер именованного канала, а затем обращается к нему с клиентскими запросами.
Вот как он выглядит:
public static async Task Go() {
// Запуск сервера немедленно возвращает управление, потому что
// сервер ожидает клиентские запросы в асинхронном режиме
StartServer(); // Возвращает void, компилятор выдает предупреждение
// Создание набора асинхронных клиентских запросов;
// сохраняем Task каждого клиента.
List> requests = new List>(10000);
for (Int32 n = 0; n < requests.Capacity; n++)
requests.Add(IssueClientRequestAsync("localhost", "Request #" + n));
// Асинхронное ожидание завершения всех клиентских запросов
// ВНИМАНИЕ: если 1+ заданий выдадут исключение,
// WhenAll заново инициирует последнее исключение
String[] responses = await Task.WhenAll(requests);
// Обработка всех запросов
for (Int32 n = 0; n < responses.Length; n++)
Console.WriteLine(responses[n]);
}
Код запускает сервер именованного канала, чтобы он начинал прослушивание
клиентских запросов, а затем в цикле
for
инициирует 10 000 запросов с макси-
мально возможной скоростью. При каждом вызове
IssueClientRequestAsync
возвращается объект
Task
, который добавляется в коллекцию, Теперь
сервер именованного канала с максимально возможной скоростью обрабатывает
эти запросы, используя потоки из пула, которые пытаются обеспечить максималь-
ную загрузку всех процессоров на машине
1
. По мере того, как сервер обрабатывает
каждый запрос, объект
Task
каждого запроса завершается со строковым
ответом, возвращенным сервером.
1
Занятное наблюдение: когда я тестировал этот код на своей машине, загрузка процессора
на моем 8-процессорном компьютере, естественно, доходила до 100 %. Так как все процес-
соры были заняты, машина нагревалась, и шум от вентилятора становился сильнее! После
завершения обработки загрузка процессора снижалась, и вентилятор тоже затихал.
810
Do'stlaringiz bilan baham: |