Я работаю над проектом, который должен поддерживать как асинхронную, так и синхронизированную версию одной и той же логики / метода. Так, например, мне нужно иметь:
public class Foo
{
public bool IsIt()
{
using (var conn = new SqlConnection(DB.ConnString))
{
return conn.Query<bool>("SELECT IsIt FROM SomeTable");
}
}
public async Task<bool> IsItAsync()
{
using (var conn = new SqlConnection(DB.ConnString))
{
return await conn.QueryAsync<bool>("SELECT IsIt FROM SomeTable");
}
}
}
Логика асинхронности и синхронизации для этих методов идентична во всех отношениях, за исключением того, что один является асинхронным, а другой - нет. Есть ли законный способ избежать нарушения принципа СУХОЙ в таком сценарии? Я видел, что люди говорят, что вы можете использовать GetAwaiter (). GetResult () для асинхронного метода и вызывать его из вашего метода синхронизации? Этот поток безопасен во всех сценариях? Есть ли другой, лучший способ сделать это, или я вынужден дублировать логику?
c#
.net
asynchronous
Marko
источник
источник
return Task.FromResult(IsIt());
)Ответы:
Вы задали несколько вопросов в своем вопросе. Я сломаю их немного иначе, чем ты. Но сначала позвольте мне прямо ответить на вопрос.
Мы все хотим, чтобы камера была легкой, качественной и дешевой, но, как говорится, вы можете получить только два из этих трех. Вы находитесь в такой же ситуации здесь. Вы хотите эффективное решение, безопасное и разделяющее код между синхронными и асинхронными путями. Вы получите только два из них.
Позвольте мне объяснить, почему это так. Начнем с этого вопроса:
Суть этого вопроса заключается в том, «могу ли я разделить синхронные и асинхронные пути, заставив синхронный путь просто выполнить синхронное ожидание в асинхронной версии?»
Позвольте мне быть предельно ясным по этому вопросу, потому что это важно:
ВЫ ДОЛЖНЫ НЕМЕДЛЕННО ОСТАНОВИТЬСЯ, ЧТОБЫ СОВЕТАТЬСЯ С ЭТИМИ ЛЮДЯМИ .
Это очень плохой совет. Очень опасно синхронно получать результат от асинхронной задачи, если у вас нет доказательств того, что задача выполнена нормально или ненормально .
Причина, по которой это крайне плохой совет, заключается в том, чтобы рассмотреть этот сценарий. Вы хотите косить газон, но лезвие вашей газонокосилки сломано. Вы решили следовать этому рабочему процессу:
Что просходит? Вы спите вечно, потому что операция проверки почты теперь зависит от того, что происходит после получения почты .
Очень легко попасть в такую ситуацию, когда вы синхронно ожидаете произвольную задачу. У этой задачи может быть запланирована работа в будущем потока, который сейчас ожидает , и теперь это будущее никогда не наступит, потому что вы его ожидаете.
Если вы делаете асинхронное ожидание, тогда все в порядке! Вы периодически проверяете почту, и пока вы ждете, вы делаете бутерброд или платите налоги или что-то еще; Вы продолжаете работать, пока ждете.
Никогда не ждите синхронно. Если задача выполнена, это не нужно . Если задача не выполнена, но запланирована для запуска из текущего потока, она неэффективна, поскольку текущий поток может обслуживать другую работу вместо ожидания. Если задача не выполнена и расписание запускается в текущем потоке, она зависает, чтобы синхронно ждать. Нет никакой веской причины для синхронного ожидания, если только вы не знаете, что задача выполнена .
Для дальнейшего чтения по этой теме, см.
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Стивен объясняет сценарий реального мира гораздо лучше, чем я.
Теперь давайте рассмотрим «другое направление». Можем ли мы поделиться кодом, сделав асинхронную версию просто выполняющей синхронную версию в рабочем потоке?
То есть , возможно , и в самом деле , вероятно , плохая идея, по следующим причинам.
Это неэффективно, если синхронная операция ввода-вывода с высокой задержкой. По сути, это нанимает работника и заставляет его спать до тех пор, пока задача не будет выполнена. Нитки безумно дороги . По умолчанию они потребляют минимум миллион байтов адресного пространства, им требуется время, они берут ресурсы операционной системы; Вы не хотите прожечь нить, делая бесполезную работу.
Синхронная операция может быть записана не как потокобезопасная.
Это является более разумным , если метод высокой латентностью работы процессора связаны, но если это , то вы , вероятно , не хотите , чтобы просто передать его в рабочий поток. Вы, вероятно, захотите использовать библиотеку параллельных задач, чтобы распараллелить ее как можно большему количеству процессоров, вы, вероятно, захотите логику отмены, и вы не можете просто заставить синхронную версию делать все это, потому что тогда это будет уже асинхронная версия .
Дальнейшее чтение; опять же, Стивен объясняет это очень четко:
Почему бы не использовать Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
Больше сценариев «делай и не делай» для Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html
Что это тогда оставляет нас с? Обе технологии для совместного использования кода приводят либо к тупикам, либо к большой неэффективности. Мы пришли к выводу, что вы должны сделать выбор. Вам нужна программа, которая является эффективной и правильной и доставляет удовольствие вызывающей стороне, или вы хотите сохранить несколько нажатий клавиш, дублируя небольшой объем кода между синхронным и асинхронным путями? Боюсь, вы не получите и то, и другое.
источник
Task.Run(() => SynchronousMethod())
в асинхронной версии не то, что хочет ОП?Dns.GetHostEntryAsync
это реализовано в .NET иFileStream.ReadAsync
для определенных типов файлов. ОС просто не предоставляет асинхронный интерфейс, поэтому среда выполнения должна имитировать его (и это не зависит от языка - скажем, среда выполнения Erlang запускает все дерево рабочих процессов с несколькими потоками внутри каждого, чтобы обеспечить неблокируемый дисковый ввод-вывод и имя разрешающая способность).Сложно дать единый ответ на этот вопрос. К сожалению, не существует простого, идеального способа повторного использования асинхронного и синхронного кода. Но вот несколько принципов для рассмотрения:
Query()
один иQueryAsync()
другой) или устанавливает соединения с разными настройками. Таким образом, даже когда он структурно похож, часто бывает достаточно различий в поведении, чтобы его можно было рассматривать как отдельный код с различными требованиями. Обратите внимание на различия между реализациями методов Async и Sync в классе File, например: не предпринимается никаких усилий, чтобы заставить их использовать один и тот же кодTask.FromResult(...)
.Удачи.
источник
Легко; пусть синхронный вызов асинхронный. Есть даже удобный способ
Task<T>
сделать это:источник