async-await threading internals

时间:2014-02-19 11:49:23

标签: c# .net asynchronous async-await

我很好奇异步等待线程内部。

每个人都声明异步在性能方面要好得多,因为它释放了等待长异步调用响应的线程。 好的,我明白了。

但是让我们考虑这种情况。

我有一个异步方法在数据库上执行异步操作。 数据库的api公开函数BeginQuery和事件QueryCompleted。 我用任务包装了那些(使用TaskCompletionSource)。

我的问题是调用BeginQuery和触发事件QueryCompleted之间的内幕。

我的意思是 - 是不是需要产生某种工作来解雇事件?在非常低的级别,它必须是一个阻止来自db的线程读取结果的同步循环。

我认为任何异步调用都必须生成一个线程来实际处理响应(可能在驱动程序代码的低级c ++循环中等待它)。

所以我们唯一的“收获”是当一些其他线程正在工作时,可以释放调用者线程。

调用异步方法总是创建一个新的工作线程吗?

有人可以证实我的理解吗?

4 个答案:

答案 0 :(得分:34)

  

每个人都声明异步在性能方面要好得多,因为它释放了等待长异步调用响应的线程。

是和否。 async背后的一点是释放调用线程。在UI应用程序中,async的主要好处是响应性,因为UI线程被释放。在服务器应用程序中,async的主要好处是可伸缩性,因为请求线程可以释放以处理其他请求。

  

所以我们唯一的“收获”是当一些其他线程正在执行其工作时,可以释放调用者线程。   总是调用异步方法是创建一个新的工作线程吗?

没有。在操作系统级别,所有I / O都是异步的。在底层异步I / O正在进行时,它是阻塞线程的同步API。我最近在一篇博文中写道:there is no thread

答案 1 :(得分:6)

  

我的意思是 - 它不需要产生某种工人来解雇   事件?在非常低的水平,它必须是一些同步循环   阻止来自db的线程读取结果。

它将创建一个IO完成端口(IOCP),表示正在外部处理的任务,该线程将继续执行其他操作。然后,当IOCP通知任务完成时,线程将获取IOCP的状态并继续执行任务。

http://www.drdobbs.com/cpp/multithreaded-asynchronous-io-io-comple/201202921

  

I / O完成端口为问题提供了优雅的解决方案   编写使用多线程的可伸缩服务器应用程序   异步I / O.

答案 2 :(得分:4)

  

我的意思是 - 它不需要产生某种工人来解雇   事件?在非常低的水平,它必须是一些同步循环   阻止来自db的线程读取结果。

即使您实际上必须等待内核对象(如手动重置事件),您仍然可以将阻塞同步代码转换为异步代码并将该线程从阻塞中释放(已更新:a real-life scenario)。 / p>

例如,同步代码:

void Consume()
{
    var completedMre = new ManualResetEvent(false);

    producer.StartOperation(completedMre);

    completedMre.WaitOne(); // blocking wait

    Console.WriteLine(producer.DequeueResult());
}

异步模拟:

async Task ConsumeAsync()
{
    var completedMre = new ManualResetEvent(false);

    producer.StartOperation(completedMre);

    var tcs = new TaskCompletionSource<Result>();

    ThreadPool.RegisterWaitForSingleObject(completedMre, 
        (s, t) => tcs.SetResult(producer.DequeueResult()),
        null, Timeout.Infinite, true);

    var result = await tcs.Task;
    Console.WriteLine(result);
}

异步版本可以扩展多达64倍(MAXIMUM_WAIT_OBJECTS,这是RegisterWaitForSingleObject可以在单个线程上等待聚合的内核对象的最大数量。因此,您可以并行调用Consume() 64次,它将阻止64个线程。或者你可以调用ConsumeAsync 64次,它只会阻止一个线程。

答案 3 :(得分:1)

异步方法不会创建新线程,它们基于Task,它可能会也可能不会使用线程(与TaskCompletionSource一样),即使它们这样做也没有开销,因为它们没有使用ThreadPool