我正在读一本书《 Terrell R.-.NET中的并发性》.
有一个不错的代码示例:
Lazy<Task<Person>> person = new Lazy<Task<Person>>(
async () =>
{
using (var cmd = new SqlCommand(cmdText, conn))
using (var reader = await cmd.ExecuteReaderAsync())
{
// some code...
}
});
async Task<Person> FetchPerson()
{
return await person.Value;
}
作者说:
Because the lambda expression is asynchronous, it can be executed on
any thread that calls Value, and the expression will run within the
context.
据我了解,线程来到FetchPerson并停留在Lamda执行中.真的不好吗?有什么后果?
作为解决方案,作者建议创建一个任务:
Lazy<Task<Person>> person = new Lazy<Task<Person>>(
() => Task.Run(
async () =>
{
using (var cmd = new SqlCommand(cmdText, conn))
using (var reader = await cmd.ExecuteReaderAsync())
{
// some code...
}
}));
真的对吗?这是一个IO操作,但是我们从Threadpool窃取了CPU线程.
解决方法:
Because the lambda expression is asynchronous, it can be executed on any thread that calls Value, and the expression will run within the context.
lambda可以从任何线程运行(除非您对允许访问Lazy的值的线程类型非常小心),因此它将在该线程的上下文中运行.这不是因为它是异步的,即使它是同步的,也可以在任何调用它的线程的上下文中运行,这都是正确的.
As i understand it, the Thread come to FetchPerson and is stuck in Lamda execution.
lambda是异步的,因此它将(如果正确实现)几乎立即返回.这就是异步的含义,因为它不会阻塞调用线程.
Is that realy bad? What consequences?
如果您错误地实现了异步方法,并使其长时间运行同步工作,那么是的,您正在阻塞该线程/上下文.如果您不这样做,那么您就不是.
另外,默认情况下,异步方法中的所有继续都将在原始上下文中运行(如果它完全具有SynchonrizationContext).在您的情况下,您的代码几乎可以肯定不依赖于重用该上下文(因为您不知道调用者可能拥有的上下文,所以我无法想象您编写了其余代码来使用它).鉴于此,您可以在等待的任何对象上调用.ConfigureAwait(false),这样就不会将当前上下文用于这些继续.为了避免浪费时间在原始上下文上安排工作,等待其他需要它的时间,或者在不必要的时候让其他任何东西等待此代码,这只是性能的微小改进.
As a solution, the author suggest to create a Task: […] Is that really correct?
它不会破坏任何东西.它将安排工作在线程池线程中运行,而不是在原始上下文中运行.首先,这会产生一些额外的开销.您只需将ConfigureAwait(false)添加到您要等待的所有内容中,就可以用较低的开销完成大约相同的事情.
This is an IO operation, but we steal CPU thread from Threadpool.
该代码段将在线程池线程上启动IO操作.由于该方法仍然是异步的,因此它将在启动后立即将其返回到池中,并在每次等待后从池中获取一个新线程以再次开始运行.后者可能适合这种情况,但是将代码启动以启动初始异步操作到线程池线程只是增加了无实际值的开销(因为操作如此短,您将花费更多的精力在线程上进行调度池线程而不是仅运行它).