C# 异步/等待的体系结构
如果您在体系结构的较低级别上使用async/await,是否有必要一直“冒泡”异步/await调用,因为您基本上是在为每一层创建一个新线程,所以效率是否很低(异步调用每一层的异步函数,还是这并不重要,只是取决于您的偏好 我正在使用EF6.0-alpha3,这样我就可以在EF中使用异步方法 我的存储库是这样的:C# 异步/等待的体系结构,c#,.net,asynchronous,async-await,c#-5.0,C#,.net,Asynchronous,Async Await,C# 5.0,如果您在体系结构的较低级别上使用async/await,是否有必要一直“冒泡”异步/await调用,因为您基本上是在为每一层创建一个新线程,所以效率是否很低(异步调用每一层的异步函数,还是这并不重要,只是取决于您的偏好 我正在使用EF6.0-alpha3,这样我就可以在EF中使用异步方法 我的存储库是这样的: public class EntityRepository:i其中E:class { 公共异步虚拟任务保存() { wait context.saveChangesSync(); } }
public class EntityRepository:i其中E:class
{
公共异步虚拟任务保存()
{
wait context.saveChangesSync();
}
}
现在,我的业务层如下所示:
public抽象类ApplicationBCBase:IEntityBC
{
公共异步虚拟任务保存()
{
等待repository.Save();
}
}
当然,我的UI中的方法在调用时也会遵循相同的模式
这是:
private异步任务Dosomething1()
{
//其他东西
...
return wait Dosomething2();
}
专用异步任务Dosomething2()
{
//其他东西
...
return wait Dosomething3();
}
专用异步任务Dosomething3()
{
//其他东西
...
返回等待任务。运行(()=>“”);
}
由于您基本上是为每一层创建一个新线程(异步地为每一层调用一个异步函数),这是不是效率低下,或者这并不重要,只是取决于您的偏好
不需要。异步方法不一定使用新线程。在这种情况下,由于底层异步方法调用是IO绑定的方法,因此实际上不应该创建新线程
这是:
如果要使操作保持异步,就必须“冒泡”异步调用。不过,这确实是首选,因为它允许您充分利用异步方法,包括在整个堆栈中将它们组合在一起
2. negative on performance
不。正如我提到的,这不会创建新线程。这会带来一些开销,但大部分开销可以最小化(见下文)
如果您想保持异步,则不需要这样做。您需要这样做以保持整个堆栈的异步
现在,您可以做一些事情来提高性能。如果您只是包装一个异步方法,您不需要使用语言功能-只需返回任务
:
public virtual Task Save()
{
return repository.Save();
}
repository.Save()
方法已经返回了一个任务
——您不需要等待它,只需将其包装回任务
。这将使该方法的效率有所提高
您还可以使用“低级”异步方法来防止它们需要调用同步上下文:
private async Task<string> Dosomething2()
{
//other stuff
...
return await Dosomething3().ConfigureAwait(false);
}
如果您正在创建一个异步方法,该方法在内部使用Task.Run
围绕一些本身不异步的内容“创建异步”,那么您实际上是在将同步代码包装到异步方法中。这将使用线程池线程,但可以“隐藏”它这样做的事实,有效地使API产生误导。通常最好将调用留给任务。为最高级别的调用运行,并让底层方法保持同步,除非它们真正能够利用异步IO或任务以外的某种卸载方式。运行。(这并不总是正确的,但通过Task.Run
、然后通过async/await返回的同步代码中的“异步”代码通常是有缺陷的设计的标志。)
如果您在体系结构的较低级别上使用async/await,是否有必要一直“冒泡”异步/await调用,因为您基本上是在为每一层创建一个新线程,所以效率是否很低(异步调用每一层的异步函数,还是这并不重要,只是取决于您的偏好
这个问题暗示了一些误解
首先,不要每次调用异步函数时都创建一个新线程
其次,您不需要声明异步方法,因为您正在调用异步函数。如果您对已返回的任务感到满意,只需从没有异步修饰符的方法返回:
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();
}
}
最好写为:
public Task<string> Foo()
{
var bar = new Bar();
bar.Baz();
return bar.Quux();
}
公共任务Foo()
{
var bar=新的bar();
bar.Baz();
返回条.qux();
}
(从理论上讲,创建的任务和调用方可以添加延续的任务之间存在非常微小的差异,但在绝大多数情况下,您不会注意到任何差异。)请注意,您也可以标记一条消息,例如您描述为async
的消息,以添加错误处理。如果您等待任务,您可以将调用包装在try/catch
块中,而不是向提供的任务添加延续。@Servy:是的,但在这种情况下,它不符合不提供额外处理的描述。记住,在不改变API的情况下,这样做很方便……因此,为了理解它,我只需要在同一函数中后面的某个函数需要返回异步方法时使用async/await?如果是fire and forget或last语句,那么就不需要await了?@Valdeteo:在这种情况下,这不是真的lly“发射并忘记”-您仍然返回一个任务
,该任务可用于查看操作何时完成、失败等。用一句话准确地总结事情是很困难的-最好深入了解等待实际上为您做了什么。@JamesManning:不,它也适用于非异步/等待版本,因为
private async Task<string> Dosomething3()
{
//other stuff
...
// Potentially a bad idea!
return await Task.Run(() => "");
}
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();
}
}
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();
}