Напишите хорошо разработанный асинхронный/неасинхронный API

Я столкнулся с проблемой разработки методов, которые выполняют сетевые операции ввода-вывода (для библиотеки многократного использования). Я прочитал этот вопрос

С# 5 await/async в дизайне API

а также другие, более близкие к моей проблеме.

Итак, вопрос в том, хочу ли я предоставить метод async и non-async, как я должен их проектировать?

Например, чтобы открыть неасинхронную версию метода, мне нужно сделать что-то вроде

public void DoSomething() {
  DoSomethingAsync(CancellationToken.None).Wait();
}

и я чувствую, что это не отличный дизайн. Я хотел бы предложить (например) предложение о том, как определить частные методы, которые могут быть обернуты в публичные, чтобы предоставить обе версии.

Ответ 1

Если вы хотите наиболее удобный вариант, используйте только async API, который реализован без каких-либо блокирующих вызовов или с использованием потоков потоков потоков.

Если вы действительно хотите иметь как async, так и синхронные API, вы столкнетесь с проблемой ремонтопригодности. Вам действительно нужно реализовать его дважды: один раз async и один раз синхронно. Оба эти метода будут выглядеть почти идентичными, поэтому первоначальная реализация будет простой, но вы получите два отдельных почти одинаковых метода, поэтому обслуживание будет проблематичным.

В частности, нет простого и простого способа просто сделать async или синхронную "обертку". У Стивена Туба есть лучшая информация по теме:

(короткий ответ на оба вопроса "нет" )

Ответ 2

Я согласен с Марксом и Стивеном (Cleary).

(BTW, я начал писать это как комментарий к Стивену, но он оказался слишком длинным, сообщите мне, нормально ли писать это как ответ или нет, и не стесняйтесь брать бит из и добавьте его в ответ Стивена, в духе "предоставления наилучшего ответа" ).

Это действительно "зависит": как сказал Марк, важно знать, как DoSomethingAsync асинхронен. Мы все согласны с тем, что нет смысла использовать метод "sync" для вызова метода "async" и "wait": это можно сделать в коде пользователя. Единственное преимущество использования отдельного метода состоит в том, чтобы иметь фактический прирост производительности, иметь реализацию, которая под капотом отличается и адаптирована к синхронному сценарию. Это особенно верно, если метод async создает поток (или принимает его из threadpool): вы получаете что-то, что под ним использует два "потока управления", в то время как "многообещающий" с его синхронным взглядом должен выполняться в контексты звонящих. Это может даже иметь проблемы concurrency, в зависимости от реализации.

Также в других случаях, таких как интенсивный ввод-вывод, о котором упоминает OP, может быть стоит иметь две разные реализации. Большинство операционных систем (конечно, Windows) имеют для разных механизмов ввода-вывода, адаптированных к двум сценариям: например, выполнение async и операции ввода-вывода имеют большие преимущества от механизмов уровня ОС, таких как порты завершения ввода-вывода, которые добавляют немного накладные расходы (не существенные, но не нулевые) в ядре (в конце концов, они должны делать бухгалтерию, отправку и т.д.) и более непосредственную реализацию для синхронных операций. Сложность кода также сильно варьируется, особенно в тех функциях, где выполняются/координируются несколько операций.

Что бы я сделал:

  • имеют несколько примеров/тестов для типичного использования и сценариев
  • посмотрите, какой вариант API используется, где и измерять. Измерьте также разницу в производительности между вариантом "pure sync" и "sync". (не для всего API, но для нескольких типичных случаев).
  • на основе измерения, решите, стоит ли дополнительная стоимость.

Это главным образом потому, что две цели как-то контрастируют друг с другом. Если вам нужен поддерживаемый код, очевидным выбором является синхронизация с точки зрения async/wait (или наоборот) (или, что еще лучше, предоставить только асинхронный вариант и позволить пользователю "подождать" ); если вам нужна производительность, вы должны реализовать две функции по-разному, использовать разные базовые механизмы (из фреймворка или из ОС). Я думаю, что это не должно меняться с точки зрения модульного тестирования, как вы фактически реализуете свой API.