我正在读一本书《 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;
}


作者说:


  由于lambda表达式是异步的,因此可以在以下位置执行
  任何调用Value的线程,该表达式将在
  上下文。


据我了解,线程进入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线程。

最佳答案

因为lambda表达式是异步的,所以它可以在任何调用Value的线程上执行,并且该表达式将在上下文中运行。


lambda可以从任何线程运行(除非您对允许访问Lazy值的线程类型有所注意),因此可以在该线程的上下文中运行。这不是因为它是异步的,即使它是同步的,也可以在任何调用它的线程的上下文中运行,这都是正确的。


  据我了解,线程来到FetchPerson并停留在Lamda执行中。


lambda是异步的,因此它将(如果正确实现)几乎立即返回。这就是异步的含义,因为它不会阻塞调用线程。


  真的不好吗?有什么后果?


如果您错误地实现了异步方法,并使其长时间运行同步工作,那么是的,您正在阻塞该线程/上下文。如果您不这样做,那么您就不是。

此外,默认情况下,异步方法中的所有继续都将在原始上下文中运行(如果它完全具有SynchonrizationContext的话)。在您的情况下,您的代码几乎可以肯定不依赖于重用该上下文(因为您不知道调用方可能拥有的上下文,所以我无法想象您编写了其余代码来使用它)。鉴于此,您可以在.ConfigureAwait(false)上的任何内容上调用await,这样就不会将当前上下文用于这些延续。为了避免浪费时间在原始上下文上安排时间,等待其他需要它的时间,或者在不必要的时候让其他任何东西等待此代码,这只是性能的微小改进。


  作为解决方案,作者建议创建一个任务:[...]真的正确吗?


它不会破坏任何东西。它将安排工作在线程池线程中运行,而不是在原始上下文中运行。首先,这会产生一些额外的开销。您只需将ConfigureAwait(false)添加到所有await中,就可以以较低的开销完成大约相同的事情。


  这是一个IO操作,但是我们从Threadpool窃取了CPU线程。


该代码段将在线程池线程上启动IO操作。由于该方法仍然是异步的,因此它将在启动后立即将其返回到池中,并在每次等待后从池中获取一个新线程以再次开始运行。后者可能适合这种情况,但是将代码启动以启动初始异步操作到线程池线程只是增加了无实际值的开销(因为操作如此短,您将花费更多的精力在线程上进行调度)池线程而不是仅运行它)。

关于c# - 任务创建开销,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54107868/

10-11 18:20