Оптимизируем async/await в библиотеках

Посмотрим, как можно немного оптимизировать работу async/await при написании собственной библиотеки с асинхронными методами.

SynchronizationContext и код приложения

При каждом вызове await сохраняется текущий SynchronizationContext. Это необходимо для возврата в исходный контекст после завершения работы асинхронного метода. Вот пример:

var data = await source.GetDataAsync();
this.UpdateInfo(data);

Если здесь вызов GetDataAsync() был сделан из UI-потока, то и UpdateInfo() должен выполнится в нем же. Такое поведение естественно для кода самого приложения и важно для корректной его работы.

А что с библиотеками?

А вот для кода библиотек все как раз наоборот. В большинстве случаев нельзя предсказать где и как будут использованы её методы. Однако, даже если это было сделано в UI-потоке, то его SynchronizationContext уже был сохранен и в дальнейшем работа будет продолжена в его же контексте.

Теперь предположим, что код библиотечного метода сам совершает асинхронные вызовы. Есть ли необходимость сохранять текущий контекст (по сути неизвестного потока)? В большинстве случаев – нет, за редкими исключениями вроде UI компонент.

API .NET 4.5 содержит метод ConfigureAwait(), который позволяет настраивать поведение await. Его единственный булевский параметр указывает на необходимость сохранения контекста (что и происходит по умолчанию). Например, отменим его в следующем вызове:  

string content = await httpClient.GetStringAsync(uri).ConfigureAwait(false);

Оптимизация в данном случае заключается в отсутствии расходов на сохранение и восстановлении SynchronizationContext. А это может быть особенно заметно, если такой метод вызывается в цикле.

Добавить комментарий