【问题标题】:c# async runs single threaded?c# async 运行单线程?
【发布时间】:2016-10-02 08:31:18
【问题描述】:

我正在阅读http://msdn.microsoft.com/en-US/library/vstudio/hh191443.aspx。 示例代码:

async Task<int> AccessTheWebAsync()
{ 
    // You need to add a reference to System.Net.Http to declare client.
    HttpClient client = new HttpClient();

    // GetStringAsync returns a Task<string>. That means that when you await the 
    // task you'll get a string (urlContents).
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

    // You can do work here that doesn't rely on the string from GetStringAsync.
    DoIndependentWork();

    // The await operator suspends AccessTheWebAsync. 
    //  - AccessTheWebAsync can't continue until getStringTask is complete. 
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync. 
    //  - Control resumes here when getStringTask is complete.  
    //  - The await operator then retrieves the string result from getStringTask. 
    string urlContents = await getStringTask;

    // The return statement specifies an integer result. 
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value. 
    return urlContents.Length;
}

页面上还写着:

async 和 await 关键字不会导致创建额外的线程。异步方法不需要多线程,因为异步方法不在自己的线程上运行

这个“没有创建额外的线程”是否适用于标记为异步的方法范围内?

我想为了让 GetStringAsync 和 AccessTheWebAsync 同时运行(否则 GetStringAsync 将永远无法完成,因为 AccessTheWebAsync 现在拥有控制权),最终 GetStringAsync 必须在与 AccessTheWebAsync 线程不同的线程上运行。

对我来说,编写异步方法仅在它等待的方法也是异步的(已经使用额外的线程并行地做自己的事情)时不添加更多线程有用。

我的理解正确吗?

【问题讨论】:

    标签: c# async-await c#-5.0


    【解决方案1】:

    这是async 强大功能的关键。 GetStringAsync 和其他自然异步操作不需要线程GetStringAsync 只是发出 HTTP 请求并注册一个回调以在服务器回复时运行。不需要一个线程来等待服务器响应。

    实际上,线程池只用了一点点。在上面的例子中,GetStringAsync注册的回调将在线程池线程上执行,但它所做的只是通知AccessTheWebAsync它可以继续执行。

    我有一个async intro blog post,你可能会觉得有帮助。

    【讨论】:

    • 你能解释一下“自然异步操作不需要线程”是什么意思吗?该工作还能如何处理?您是在谈论单个线程的时间在任务之间分配吗?
    • @VincePanuccio:在评论中描述有点长,但我有 a blog post 详细说明。
    • @JustAMartin:正如我在async intro 中提到的,默认情况下await 会捕获一个上下文并使用它来恢复执行,这个上下文是用于UI 应用程序的SynchronizationContextSynchronizationContext here 上有更多信息,但要点是当 async 方法在 UI 线程上恢复时,它通过向 WinMain 发送自定义 Win32 消息来实现。
    • @JustAMartin:控制台应用程序完全不同。 async Main 实际上是通过阻塞从Main 返回的Task 来实现的。
    • @JustAMartin:没有解释(或记录),因为它是您正在使用的框架的实现细节。我的知识来自反编译 BCL,但现在 WinFormsWPF 都是开源的。
    【解决方案2】:

    我想为了让 GetStringAsync 和 AccessTheWebAsync 同时运行 ...

    它们不会同时运行(至少不是按照您的想法)。现在,如果 HttpClient.GetStringAsync 本身

    • 在不同的线程上开始工作,然后该代码可以同时运行,-或-
    • 如果有一个等待(使用 ConfigureAwait(false)),那么该方法中的剩余工作将被安排到线程池线程上(这样代码就可以同时运行。

    关键是,将方法声明为async 和/或使用await,不会导致您正在创作的方法在单独的线程上运行(您必须明确地这样做)。

    注意: 如果没有另一个异步方法的代码(或文档),你真的不知道它有多少是同步运行的。直到该方法执行await 或显式在另一个线程上开始工作(通常通过启动新的Task),它才真正开始异步

    【讨论】:

      【解决方案3】:

      如果有必要,可以从线程池中取出一个线程用于操作。在控制台的情况下(不讨论 UI 线程或 IO 线程),当前线程进入控制台,另一个线程被用于执行,这个新线程是执行剩余操作的线程。

      【讨论】:

        【解决方案4】:

        作者在这里说的是,简单地在某处使用 async 和 await 关键字不会导致当前方法在不同的线程上运行。让我们调查一下提供的代码中发生了什么。

        async Task<int> AccessTheWebAsync()
        { 
            // You need to add a reference to System.Net.Http to declare client.
            HttpClient client = new HttpClient();
        
            // GetStringAsync returns a Task<string>. That means that when you await the 
            // task you'll get a string (urlContents).
            Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
        

        到目前为止,所有内容都在单个线程中执行。此时,client.GetStringAsync 调用可以衍生出一个新线程(尽管这不是任何异步方法的保证)。应该注意的是,虽然 GetStringAsync 方法本质上是在当前线程上启动的(例如实际进行调用),但读取响应一旦返回就会在不同的线程中执行。这由它返回一个 Task 对象来表示。 Task 可能代表已经完成的工作,当前正在执行的辅助线程,或者只是计划稍后执行的工作单元。

        DoIndependentWork();
        

        这现在在我们的主线程上执行,在调用已排队(或可能发送)之后。因此,这可能在等待返回请求时发生。请注意,我们仍然在我们的主线程上。

         string urlContents = await getStringTask;
        

        此时,我们的线程返回。 .NET 将创建一个包含方法剩余部分的延续函数(返回 urlContents.Length;),一旦 getStringTask 在它自己的线程上完成,它将自动为我们调用。到那时,一个新线程将接续。

        通过所有这些,我们在 async 方法中编写的所有内容,直到 await 关键字,都在一个线程中运行。当然,我们调用了另一个方法,它可能碰巧产生另一个线程,但是我们没有做任何事情来创建另一个线程,只是使用 async/await 关键字。最后的延续可能由不同的线程调用,但是在我们的初始函数实际返回之后(这就是为什么我们的函数返回一个 Task 对象,以表示当对该函数的任何调用返回时可能尚未完成的工作) .

        【讨论】:

        • 这个答案是完全错误的。 Task 对象不一定代表代码;特别是,GetStringAsync 永远不会启动另一个线程来等待服务器。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-04
        • 1970-01-01
        相关资源
        最近更新 更多