Архитектура для 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 часто является признаком дефектного дизайна.)