【问题标题】:Time consuming tasks inside async/await methodasync/await 方法中的耗时任务
【发布时间】:2014-09-06 10:16:43
【问题描述】:

与线程相比,我不太了解异步/等待的好处。

如果在一个方法内部,我有一个没有异步/等待版本的操作,它在其他异步操作中间消耗了一些时间,比如 20 毫秒。

调用此方法 1000 次,由于 async/await 仅在一个线程内执行,因此在我的测试中至少会消耗 20ms x 1000 次(使用 Thread.Sleep 进行模拟)。

对 async/await 有什么误解吗?

public void Start()
{
    Task[] task = new Task[500];

    for (int i = 0; i < 500; i++)
    {
        task[i] = AsyncGet();
    }

    await Task.WhenAll(task);
}
public async Task<bool> AsyncGet()
{
    HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create("http://www.example.com");
    req.Method = "GET";
    using(HttpWebResponse res = (HttpWebResponse)await req.GetResponseAsync())
    {

        Thread.Sleep(20);//Simulate an operation without async/await version that consume a little bit time

        using (Stream responseStream = res.GetResponseStream())
        {
             StreamReader sr = new StreamReader(responseStream, Encoding.ASCII);
             string resp = await sr.ReadToEndAsync();
        }
    }
}

更新问题

如果我需要在使用外部库获得 http 响应后进行一些非异步/等待操作,例如操作 json。避免浪费时间的最佳做法是什么?

【问题讨论】:

  • 为什么只有 1 个线程,它使用线程池中的任何可用线程。
  • 你通常会尽量避免非异步的持久操作。或者,如果同步上下文无关紧要,您可以在返回的 Task 对象上使用 ConfigureAwait(false) 让继续在任何上下文上继续。
  • @eranotzap 发布的代码在 1 个线程上运行。 GetResponseAsync 可能在内部使用另一个线程,但鉴于这是一个 IO 操作,他们更有可能使用类似 IO 完成端口之类的东西。
  • 我写评论时没有代码。
  • @Dirk 看起来像 Sriram 在下面的回答。回调可能发生在线程池中的多个线程上。

标签: c# .net multithreading performance async-await


【解决方案1】:

不,不一定。如果您从单线程的 SynchronizationContext 调用 Start 方法(比如任何 UI 上下文,如 WindowsFormsSynchronizationContext 或 DispatcherSynchronizationContext),答案是肯定的 [*]。

如果您从其他线程(例如工作线程)调用Start。它不会花费 20ms x 1000。因为在这种情况下,await 之后的代码将在ThreadPool thread 中执行。所以在等待之后可以有 N 个线程执行代码。在您的情况下,N 将小于或等于 500。

也就是说,您不应该阻塞异步方法。如果你需要在异步方法中使用任何同步代码,你应该使用await Task.Run(()=&gt; CodeThatBlocks())

* 如果 SynchronizationContext 没有被SynchronizationContext.SetSynchronizationContext覆盖

【讨论】:

    【解决方案2】:

    对 async/await 有什么误解吗?

    是的,您似乎有误会。您无法使用 Thread.Sleep 模拟 async-await 行为,这是一个阻塞调用。当你await Task.WhenAll 时,多个异步请求同时执行,忽略底层线程。

    如果您想同时卸载多个请求,请使用await Task.Delay 对其进行测试,您会注意到行为的变化。

    您可以更进一步,在调用GetResponseAsync 时使用ConfigureAwait(false),这样您的方法的延续就不会将工作编组回原来的SynchronizationContext(这在处理诸如WinFormSynchronizationContext 之类的自定义方法时很重要)和继续在 IOCP 上执行。

    【讨论】:

    • 您的回答也很有帮助。我希望我可以设置两个接受的答案
    【解决方案3】:

    不管任何 async / await 结构如何,休眠一个线程仍然会休眠该线程。 async / await 的想法是让每个线程尽可能地被占用,以避免必须创建大量线程来等待 I/O。在后台执行 CPU 密集型操作仍需要您创建线程。

    换句话说,您提供的示例将导致线程冻结 20 毫秒,因为它不是 async/await 的预期用例。

    换句话说,要么:

    • 只需使用异步 I/O。不要进行任何 CPU 密集型处理(也不要使用睡眠)。
    • 使用线程池。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-19
      • 1970-01-01
      • 1970-01-01
      • 2021-01-26
      • 2017-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多