val*_*ero 58 .net c# asynchronous async-await c#-5.0
如果你在架构中的较低级别使用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();
}
}
Run Code Online (Sandbox Code Playgroud)
现在我的业务层是这样的:
public abstract class ApplicationBCBase<E> : IEntityBC<E>
{
public async virtual Task Save()
{
await repository.Save();
}
}
Run Code Online (Sandbox Code Playgroud)
当然,我的UI中的方法在调用时会遵循相同的模式.
这是:
即使在单独的图层/项目中没有使用它,如果我在同一个类中调用嵌套方法,则同样的问题也适用:
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(() => "");
}
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 58
如果你在架构中的较低级别使用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();
}
}
Run Code Online (Sandbox Code Playgroud)
这样会稍微提高效率,因为它不会涉及很少创建状态机 - 但更重要的是,它更简单.
任何异步方法,你有一个await表达式等待a Task或者Task<T>,在方法结束时没有进一步处理,最好不使用async/await写入.所以这:
public async Task<string> Foo()
{
var bar = new Bar();
bar.Baz();
return await bar.Quux();
}
Run Code Online (Sandbox Code Playgroud)
写得更好:
public Task<string> Foo()
{
var bar = new Bar();
bar.Baz();
return bar.Quux();
}
Run Code Online (Sandbox Code Playgroud)
(理论上,创建的任务有一个非常小的差异,因此呼叫者可以添加延续,但在绝大多数情况下,你不会注意到任何差异.)
Ree*_*sey 28
它是低效的,因为你基本上是为每一层创建一个新线程(异步调用每个层的异步函数,或者它真的不重要,只是取决于你的偏好?
不可以.异步方法不一定使用新线程.在这种情况下,由于底层异步方法调用是IO绑定方法,因此确实不应该创建新线程.
这是:
1. necessary
Run Code Online (Sandbox Code Playgroud)
如果要保持操作异步,则必须"冒泡"异步调用.但这确实是首选,因为它允许您充分利用异步方法,包括在整个堆栈中将它们组合在一起.
2. negative on performance
Run Code Online (Sandbox Code Playgroud)
没有.正如我所提到的,这不会创建新线程.有一些开销,但其中大部分可以最小化(见下文).
3. just a matter of preference
Run Code Online (Sandbox Code Playgroud)
如果你想保持这种异步,那就不是了.您需要这样做才能在堆栈中保持异步.
现在,您可以采取一些措施来改善性能.这里.如果您只是包装异步方法,则不需要使用语言功能 - 只需返回Task:
public virtual Task Save()
{
return repository.Save();
}
Run Code Online (Sandbox Code Playgroud)
该repository.Save()方法已经返回Task- 您不需要等待它只是将它包装回来Task.这将使该方法更有效.
您还可以使用"低级"异步方法ConfigureAwait来防止它们需要调用同步上下文:
private async Task<string> Dosomething2()
{
//other stuff
...
return await Dosomething3().ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)
await 如果您不需要担心调用上下文,这可以显着减少每个开销.这通常是处理"库"代码时的最佳选择,因为"外部" await将捕获UI的上下文.库的"内部"工作通常不关心同步上下文,因此最好不要捕获它.
最后,我要警告你的一个例子:
private async Task<string> Dosomething3()
{
//other stuff
...
// Potentially a bad idea!
return await Task.Run(() => "");
}
Run Code Online (Sandbox Code Playgroud)
如果你正在制作一个异步方法,它在内部Task.Run用于"创建异步",而不是本身异步的东西,那么你有效地将同步代码包装成异步方法.这将使用ThreadPool线程,但可以"隐藏"它正在这样做的事实,有效地使API误导.通常最好将调用留给Task.Run最高级别的调用,并让底层方法保持同步,除非它们真正能够利用异步IO或除了之外的某种卸载方式Task.Run.(这并非总是如此,但是"异步"代码包裹在同步代码上Task.Run,然后通过async/await返回通常是设计有缺陷的标志.)