【问题标题】:Is Async/Await using Task.Run starting a new thread asynchronously?Async/Await 是否使用 Task.Run 异步启动新线程?
【发布时间】:2015-12-19 23:41:47
【问题描述】:

我看了很多文章,仍然无法理解这部分。

考虑这段代码:

    private async void button1_Click(object sender, EventArgs e)
    {
        await Dosomething();
    }

    private async Task<string> Dosomething()
    {
        await Task.Run((() => "Do Work"));
        return "I am done";
    }

第一个问题:

当我单击按钮时,它将调用 DoSomething 并等待一个任务,该任务通过调用 Task.Run 从线程池创建一个线程(如果我没记错的话),所有这些都是异步运行的。所以我实现了创建一个线程来完成我的工作但异步执行它?但是考虑到我不需要返回任何结果,我只想完成工作而不返回任何结果,是否真的需要使用 async/await ,如果需要,如何使用?

第二个问题:

当异步运行线程时,它是如何工作的?它是在主 UI 上运行但在单独的线程上运行,还是在单独的线程上运行并且在该方法内是异步的?

【问题讨论】:

  • 我对 async/await 还不是很熟悉,但我相信是否使用新线程取决于操作系统的决定 - 即,不能保证在 async/await 上执行新线程。
  • @Tim Nit:如果被告知操作系统将启动一个新线程。任何相关的线程池等都发生在 .NET 本身中。
  • @user2864740 - 感谢您的澄清 :)
  • async/await 永远不会创建新线程 - 否则我们都必须在 awaited 调用之后为所有内容编写线程安全的安全代码,这正是 async await 试图避免的。 awaited 方法本身是否产生一个新线程 async/await 无关,被调用的 awaited 总是运行在调用它的同一个线程上。
  • @markmnl - 但是,任何在这里阅读您的评论的人也应该阅读您和 Matias under his answer 之间的讨论,其中明确指出,等待完成后的 继续 可能在不同的线程上,具体取决于当前的 SynchronizationContext。幸运的是,对于 UI 线程,上下文确实确保返回到该 UI 线程。所以对于 UI,它绝对是一个线程。 [我的意思是,如果做await,但不要Task.Run。]

标签: c# multithreading async-await


【解决方案1】:

Task&lt;TResult&gt; 类型要求您从任务中返回 TResult。如果您没有要返回的内容,则可以使用 Task 代替(顺便提一下,它是 Task&lt;TResult&gt; 的基类)。

但请记住,任务不是线程。 A task is a job to be done, while a thread is a worker. 当您的程序运行时,工作和工作人员变得可用和不可用。在幕后,库会将您的作业分配给可用的工作人员,并且由于创建新工作人员是一项昂贵的操作,它通常更愿意通过线程池重用现有工作人员。

【讨论】:

  • 感谢您的回答。但是在线程池上的工作人员上启动新任务,实际上将使用与 UI 线程不同的线程。我真的看不出更多使用 async/await 的理由,而不是我想启动一个任务并等待它完成,然后再完成下一句代码。 ://
  • @syncis 有什么问题?
  • 使用await 的真正原因之一是隐藏所有通常需要将结果编组回特定线程(例如UI线程)的丑陋样板代码。可等待任务具有“同步上下文”。如果这是默认设置,那么它将使用线程池来安排延续——任何线程都可以。但是,如果您有与例如关联的同步上下文WPF,而你 await 来自 UI 线程,则延续将自动分派回 UI 线程。 void button_Click(..) { UIThreadStuff(); await WorkerThreadStuffAsync(); MoreUIThreadStuff(); }
  • 当我们写await funconeAsync()await Task.Run(funcone)时,函数是运行在UI线程还是单独线程?
  • @variable 当你 await 一个 async 方法时,它将同步运行,直到它遇到第一个 await。所以这取决于funconeAsync()中的第一个await之前是否有代码。如果在第一个await 之前有代码,那么await funconeAsync() 将导致funconeAsync() 同步运行,那么在funconeAsync() 内部等待的方法将在不同的线程(不是UI)上运行,但是@ 内部的代码如果不使用ConfigureAwait(false),987654342@ 只会在 UI 线程上运行。 await Task.Run(funcone) 将导致 funcone 中的任何代码无法在 UI 上运行。
【解决方案2】:
  1. 创建 Async 方法的目的是让您可以稍后等待它们。有点像“我要把这水烧开,把剩下的汤料准备好,然后回到锅里等水烧开,我就可以做晚饭了。”您开始水沸腾,它在您做其他事情时异步执行,但最终您必须停下来等待它。如果您想要“即发即弃”,则不需要 Async 和 Await。

Simplest way to do a fire and forget method in C#?

  1. 启动一个新任务将该任务排入队列以便在线程池线程上执行。线程在进程的上下文中执行(例如,运行您的应用程序的可执行文件)。如果这是在 IIS 下运行的 Web 应用程序,则该线程是在 IIS 工作进程的上下文中创建的。该线程与主执行线程分开执行,因此无论您的主执行线程在做什么,它都会继续执行它的工作,同时,您的主执行线程会继续自己的工作。

【讨论】:

  • 感谢您的回答。因此,使用“await Task.Run((() => "Do Work"));”的最大原因之一是是我想启动一个做某事的任务,我想等待它完成,然后再继续下一句代码。我在这里吗?那么在执行“await Task.Run((() => "Do Work"));”时它会只启动 1 个线程吗?还是启动 2 个线程?
  • 没错。 await 的意思是“在这里停止执行,直到我的异步任务完成”并阻塞调用线程,直到异步线程完成。我在现实生活中使用它来缓存来自应用程序启动时来自 Web 服务的数据。应用程序将触发 5 次对不同服务的异步调用,然后等待它们全部返回,然后再继续。这允许所有调用同时发生,但仍然阻塞,直到它们全部完成。无论您是使用 Async/Await 还是仅使用 Task.Run,​​任务都会在单独的线程池线程上启动。
  • 对我的问题的最简单的回答,我能理解,非常感谢。
  • @DVK await 不会阻塞当前线程。 async/await 作为关键字与并发无关。 await 将简单地检查任务是否完成。如果是,则继续执行。如果不是,则该方法将未完成的任务返回到调用堆栈中的下一个方法。 async/await 的要点之一是在等待异步操作完成时不必阻塞当前线程。
  • @DVK 我指的是您声称 await 阻塞当前线程的 cmets,暗示 async/await 与线程有任何关系,甚至 await产生线程等。所有这些实际上都是错误的。
【解决方案3】:

1

如果你不await Task 或者你await 它会有很大的不同:

  • 案例你没有awaitDoSomething 被调用但下一句在DoSomething Task 尚未完成时执行。

  • 案例awaitDoSomething被调用,下一句被执行一次DoSomethingTask已经完成。

所以,async/await 的需求将取决于你想如何称呼DoSomething:如果你不叫await,那就像叫它即用即忘方式 em>。

2

它是在主 UI 上但在单独的线程上运行还是在运行 在一个单独的线程上,并且独立在里面是异步的 方法?

异步代码有时意味着其他线程(请参阅此 Q&A Asynchronous vs Multithreading - Is there a difference?)。也就是说,如果代码在与 UI 不同的线程中执行,或者它让 UI 线程在恢复时继续处理,这很好,因为 UI 循环仍然可以在其他任务正在完成时更新屏幕并行而不冻结 UI。

异步方法(即async 方法)是一种语法糖,用于告诉编译器await 语句应被视为状态机。 C# 编译器将您的 async/await 代码转换为状态机,其中等待 Task 结果的代码在等待的代码之后执行。

有趣的问答

您可能想查看这些其他问答:

OP 说...

[...] 但这是否意味着“async/await”会触发一个线程并且 Task.Run 也会触发一个线程,还是它们都是同一个线程?

使用async-await 并不意味着“我创建了一个线程”。它只是以优雅的方式实现延续的语法糖。任务可能是也可能不是线程。例如,Task.FromResult(true) 创建了一个伪任务来实现异步方法,而无需创建线程:

public Task<bool> SomeAsync()
{
    // This way, this method either decides if its code is asynchronous or 
    // synchronous, but the caller can await it anyway!
    return Task.FromResult(true);
}

【讨论】:

  • async/await 永远不会创建新线程 - 它在调用它的同一线程上执行延续。调用的可等待方法是否在新线程异步等待上执行是无视的
  • @markmnl 所以这就是我的回答...参见 OP 所说的部分。除了你绝对错误的事情:async/await 永远不会创建一个线程,但是当执行暂停时它总是在同一个线程上恢复是不正确的。
  • @MatíasFidemraizer async await 永远不会在不同的线程上恢复,如果 awaitable 方法产生一个新线程并在后台运行某些东西,那么返回与调用者无关 - 关键是它何时返回调用者在调用它的同一线程上恢复。这就是异步等待的重点——调用者没有写多线程代码。
  • @MatíasFidemraizer 我现在看到的不一定取决于当前的上下文,我已更正:) *.com/questions/21838651/…
  • @markmnl 在 ASP.NET WebAPI/MVC Core 中,await 在另一个线程池线程上恢复这一事实是一个很大的优势,尽管 UI 世界提供了简单性(如您之前已经指出),任何具有共享资源的代码默认情况下都应该是线程安全的,否则它可能在 WPF 中运行良好,但在 ASP.NET WebAPI 中可能导致彻底的灾难。
最近更新 更多