Архитектура для async / await
Если вы используете async / await на более низком уровне в своей архитектуре, необходимо ли "пузырить" вызовы async/await полностью, это неэффективно, поскольку вы в основном создаете новый поток для каждого слоя (асинхронно вызывая асинхронную функцию для каждого слоя, или это действительно не имеет значения и просто зависит от ваших предпочтений?
Я использую EF 6.0-alpha3, так что я могу иметь асинхронные методы в EF.
мой репозиторий такие:
public class EntityRepository<E> : IRepository<E> where E : class
{
public async virtual Task Save()
{
await context.SaveChangesAsync();
}
}
Теперь мой бизнес-слой так:
public abstract class ApplicationBCBase<E> : IEntityBC<E>
{
public async virtual Task Save()
{
await repository.Save();
}
}
и тогда, конечно, мой метод в моем пользовательском интерфейсе будет иметь тот же шаблон при вызове.
это:
- необходимые
- отрицательно на производительности
- просто вопрос предпочтений
даже если это не используется в отдельных слоях/проектах, те же вопросы применяются, если я вызываю вложенные методы в одном и том же класс:
private async Task<string> Dosomething1()
{
//other stuff
...
return await Dosomething2();
}
private async Task<string> Dosomething2()
{
//other stuff
...
return await Dosomething3();
}
private async Task<string> Dosomething3()
{
//other stuff
...
return await Task.Run(() => "");
}
2 ответа:
если вы используете async / await на более низком уровне в своей архитектуре, необходимо ли "пузырить" вызовы async/await полностью, это неэффективно, так как вы в основном создаете новый поток для каждого слоя (асинхронно вызывая асинхронную функцию для каждого слоя, или это действительно не имеет значения и просто зависит от ваших предпочтений?
этот вопрос предполагает несколько областей непонимания.
во-первых, вы не создать новый поток при каждом вызове асинхронной функции.
во-вторых, вам не нужно объявлять асинхронный метод только потому, что вы вызываете асинхронную функцию. Если вы довольны задачей, которая уже возвращается, просто верните ее из метода, который не есть модификатор async:
public class EntityRepository<E> : IRepository<E> where E : class { public virtual Task Save() { return context.SaveChangesAsync(); } } public abstract class ApplicationBCBase<E> : IEntityBC<E> { public virtual Task Save() { return repository.Save(); } }
этой будет быть немного более эффективным, так как это не связано с созданием государственной машины по очень небольшой причине-но что более важно, это проще.
любой асинхронный метод, где у вас есть один
await
выражение в ожиданииTask
илиTask<T>
, прямо в конце метода без дальнейшей обработки, было бы лучше писать без использования async/await. Так вот:public async Task<string> Foo() { var bar = new Bar(); bar.Baz(); return await bar.Quux(); }
лучше написать так:
public Task<string> Foo() { var bar = new Bar(); bar.Baz(); return bar.Quux(); }
(в теории есть очень небольшая разница в создаваемых задачах и, следовательно, какие абоненты могут добавлять продолжения но в подавляющем большинстве случаев, вы не заметите никакой разницы.)
это неэффективно, так как вы в основном создаете новый поток для каждого слоя (асинхронно вызывая асинхронную функцию для каждого слоя, или это действительно не имеет значения и просто зависит от ваших предпочтений?
нет. Асинхронные методы не обязательно используют новые потоки. В этом случае, поскольку основной вызов асинхронного метода является связанным методом ввода-вывода, действительно не должно быть никаких новых потоков.
Is это:
1. necessary
необходимо "пузырить" асинхронные вызовы, если вы хотите сохранить операцию асинхронной. Однако это действительно предпочтительно, поскольку позволяет полностью использовать асинхронные методы, в том числе составлять их вместе по всему стеку.
2. negative on performance
нет. Как я уже упоминал, это не создает новых потоков. Есть некоторые накладные расходы, но многое из этого можно свести к минимуму (см. ниже).
3. just a matter of preference
нет, если вы хотите сохранить эту асинхронность. Вам нужно сделать это, чтобы сохранить асинхронность в стеке.
теперь, есть некоторые вещи, которые вы можете сделать, чтобы улучшить perf. здесь. Если вы просто обертываете асинхронный метод, вам не нужно использовать языковые функции - просто верните
Task
:public virtual Task Save() { return repository.Save(); }
The
repository.Save()
метод уже возвращает aTask
- вам не нужно ждать его просто обернуть его обратно вTask
. Это позволит сохранить метод несколько больше эффективный.вы также можете использовать свои "низкоуровневые" асинхронные методы
ConfigureAwait
чтобы предотвратить их от необходимости вызова контекста синхронизации:private async Task<string> Dosomething2() { //other stuff ... return await Dosomething3().ConfigureAwait(false); }
это значительно снижает накладные расходы, связанные с каждым
await
если вам не нужно беспокоиться о вызывающем контексте. Это, как правило, лучший вариант при работе с кодом "библиотека", так как "внешний"await
захватит контекст пользовательского интерфейса. Внутренняя работа библиотеки обычно не заботятся о контексте синхронизации, поэтому лучше не захватывать это.наконец, я бы предостерег от одного из ваших примеров:
private async Task<string> Dosomething3() { //other stuff ... // Potentially a bad idea! return await Task.Run(() => ""); }
если вы делаете асинхронный метод, который, внутренне, используя
Task.Run
чтобы "создать асинхронность" вокруг чего-то, что само по себе не асинхронно, вы эффективно обертываете синхронный код в асинхронный метод. Это будет используйте поток ThreadPool, но можете "скрыть" тот факт, что он делает это, эффективно вводя API в заблуждение. Часто лучше оставить звонок наTask.Run
для ваших вызовов самого высокого уровня и пусть базовые методы остаются синхронными, если они действительно не могут воспользоваться асинхронным вводом-выводом или некоторыми средствами разгрузки, отличными отTask.Run
. (Это не всегда верно, но "асинхронный" код, обернутый синхронным кодом черезTask.Run
, затем возвращается через async / await часто является признаком дефектного дизайна.)