在.NET 4.5中,异步和同步的区别是什么?

20
在我阅读.NET 4.5中的异步编程时,看到了asyncawait关键字。在这里链接有以下段落:

处理异步请求

在启动时或并发请求数量猛增(并发度突然增加)的Web应用程序中,将这些Web服务调用设置为异步将提高您的应用程序的响应能力。异步请求处理与同步请求处理所需的时间相同。例如,如果一个请求进行了需要2秒钟才能完成的Web服务调用,则无论是同步还是异步执行该请求,它都需要2秒钟。但是,在异步调用期间,线程不会被阻塞,而是可以响应等待第一个请求完成时的其他请求。因此,在存在许多并发请求调用长时间运行操作时,异步请求可以防止请求排队和线程池增长。

对于粗体字的部分,“异步请求处理与同步请求处理所需的时间相同”是什么意思呢? 例如:
public async Task MyMethod()
{
    Task<int> longRunningTask = LongRunningOperation();
    //indeed you can do independent to the int result work here 

    //and now we call await on the task 
    int result = await longRunningTask;
    //use the result 
    Console.WriteLine(result);
}

public async Task<int> LongRunningOperation() // assume we return an int from this long running operation 
{
    await Task.Delay(1000); //1 seconds delay
    return 1;
}

据我了解,LongRunningOperation() 从这里开始执行:Task<int> longRunningTask = LongRunningOperation(); ,一旦调用 await 就会返回值,因此在我的观点中异步代码比同步代码更快,是这样吗?

另一个问题:

据我了解,主线程正在执行 MyMethod(),不会被阻塞等待 LongRunningOperation() 完成,而是返回到线程池中为其他请求提供服务。那么还有另一个线程被分配给 LongRunningOperation(); 来执行它吗?

如果是这样,那么异步编程和多线程编程之间有什么区别?

更新:

假设代码变成了这样:

public async Task MyMethod()
    {
        Task<int> longRunningTask = LongRunningOperation();
        //indeed you can do independent to the int result work here 
        DoIndependentWork();
        //and now we call await on the task 
        int result = await longRunningTask;
        //use the result 
        Console.WriteLine(result);
    }

    public async Task<int> LongRunningOperation() // assume we return an int from this long running operation 
    {
        DoSomeWorkNeedsExecution();
        await Task.Delay(1000); //1 seconds delay
        return 1;
    }

在这种情况下,在执行DoIndependentWork()期间,LongRunningOperation()会由另一个线程执行吗?

1
你缺少一些重要的基础知识,很难给出一个好的答案。你需要一个教程。了解一下 await 的作用。 - usr
执行工作总是需要相同的时间,异步代码只是以不同的顺序执行工作。需要更长时间的任务可能会被转移到另一个线程中。因此,执行的任务仍然需要时间,但应用程序不必等待任务完成。 - David
3个回答

23

异步操作并不会更快。如果你异步等待10秒钟(即await Task.Delay(10000))或同步等待(即Thread.Sleep(10000)),花费的时间都是相同的10秒。唯一的区别在于,前者在等待时不会阻塞线程,而后者会阻塞线程

现在,如果你启动一个任务并不立即等待它完成,你可以使用同一线程做其他工作,但这不会“加速”异步操作的运行:

var task = Task.Delay(10000);
// processing
await task; // will complete only after 10 seconds

关于你的第二个问题,Task.Delay(和其他真正异步操作一样)不需要线程来执行,所以没有线程Task.Delay 是使用一个 System.Threading.Timer 实现的,你启动它并在它完成时触发一个事件,在此期间它不需要线程,因为没有要执行的代码。

所以当运行 MyMethod 的线程到达 await longRunningTask 时,它就被释放了(只要 longRunningTask 还没有完成)。如果它是线程池线程,则会返回到线程池,可在应用程序中处理其他代码。


关于更新,流程如下:

  • MyMethod 开始处理
  • LongRunningOperation 开始处理
  • DoSomeWorkNeedsExecution 在调用线程上执行
  • LongRunningOperation 中遇到 await,然后返回一个热任务。
  • DoIndependentWork 被同一个调用线程执行(LongRunningOperation 仍在“运行”,不需要线程)
  • MyMethod 中遇到 await。如果原始任务已完成,则同一线程会继续同步进行,否则将返回一个最终会完成的热任务。

因此,你使用async-await允许你使用一个本来会被阻塞等待同步执行CPU密集工作的线程。


感谢l3arnon的快速回复,“同时它不需要线程,因为没有要执行的代码。” 对于这个问题,假设longRunningTask有大量要执行的代码,那么除了主线程外,另一个线程将被分配给它吗? - Abraham Josef
如果这项工作需要大量的CPU资源(即不是异步的),那么有两个选择。如果在await之前,它将在调用线程上运行。如果在await之后,则会根据SynchronizationContext在一个线程上运行(通常没有这样的上下文,因此将使用线程池线程)。 - i3arnon
如果工作是异步的(例如I/O),则不需要线程。 - i3arnon
1
@l3arnon 非常感谢您。 - Abraham Josef
@i3arnon,我可以问一下哪个更有益吗? - FaizanHussainRabbani
@FaizanRabbani 异步操作相对于同步操作而言有什么优势?如果可能的话,异步操作通常更可取,但前提是该操作本身固有异步性(例如 I/O)。 - i3arnon

5

考虑以下区别:

Thread.Sleep(1000);

并且

await Task.Delay(1000);

两种方法都需要一秒钟才能运行。然而,在前一种情况下,当前线程将被阻塞(并且所有其资源都无用),而在后一种情况下,当前线程可以执行其他有用的操作(例如,提供另一个请求)。

异步性不是为了加速单个指令序列,而是为了能够在同步代码会阻塞的情况下执行任务。

关于另一个问题

释放的线程将用于其他事情;直到操作完成之前,不会分配任何线程。这是可能的,因为底层操作系统本身是异步的。在上面的示例中,使用计时器发出信号以便线程在空闲时拾取,而不是停止内部线程。


2
(在I3arnon的回答基础上)
并不是绝对正确的说同步操作和使用async-await的操作将总体花费相同的时间。 async-await中涉及到一些额外的逻辑。需要检查已完成的awaiter和状态机。这使得一些异步操作比相应的同步操作花费更多的时间。
另一方面,大多数适合async-await的操作都是自然异步的,并且需要一些额外的处理才能使其看起来和感觉像同步。在这些情况下,异步操作所需的时间比同步操作少。
问题中的引用与Web应用程序有关。对于Web应用程序,异步操作更多地涉及在可接受的时间内服务最大数量的请求,而不是每个请求节省几微秒的时间。另一方面,如果涉及上下文切换,则会花费更多的时间,这就是为什么在Web应用程序中使用Task.Run会对应用程序造成更多的伤害而不是好处。
如果您想了解更多关于async-awit的信息,请阅读我的async-awit策划文章

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接