【问题标题】:"New Thread" and Async [duplicate]“新线程”和异步 [重复]
【发布时间】:2016-04-04 03:09:38
【问题描述】:

我使用下面的代码做了一个示例来模拟并发:

var threads = new Thread[200]; 
//starting threads logic
for (int i = 0; i < 200; i++)
         {
             threads[i].Start();
         }
         for (int i = 0; i < 200; i++)
         {
             threads[i].Join();
         } 

该代码应该向数据库中插入数千条记录,并且似乎运行良好,因为线程几乎同时完成。

但是,当我使用:

 var tasks = new List<Task<int>>();
        for (int i = 0; i < 200; i++)
        {
            tasks.Add(insert(i));
            //   await insert(i);
        }
        int[] result = await Task.WhenAll(tasks);

完成同样的逻辑需要很长时间。

有人可以向我解释一下有什么区别吗?我认为Await 应该创建线程。

【问题讨论】:

  • 没有。 await 不会创建线程。 blog.stephencleary.com/2012/02/async-and-await.html 在这种情况下,异步一词与多线程完全无关。
  • 你在代码的哪里开始你的任务?
  • 你知道启动一个线程会消耗超过 1MB 的内存吗?所以 200 远远超过 200MB。那是没有做任何处理的。并且启动线程很慢。从线程池中使用它们要好得多。
  • 谢谢大家,你们的回复真的很有帮助

标签: c# multithreading asynchronous async-await


【解决方案1】:

如果您需要复制您原来的基于Thread 的行为,您可以使用Task.Factory.StartNew(... , TaskCreationOptions.LongRunning) 来安排您的工作,然后阻塞直到通过Task.WaitAll 完成工作任务。我不推荐这种方法,但就行为而言,这将非常接近您的代码之前的工作方式。

关于为什么在您的场景中可能无法获得预期性能的更深入分析如下:

解释,第 1 部分async 并不意味着“在不同的线程上”)

标有async 关键字的方法不会神奇地异步运行。它们只是能够将等待操作(可能会或可能不会异步运行)组合成一个更大的单元(通常是TaskTask&lt;T&gt;)。

如果您的insert 方法是async,它仍然可能至少同步执行一些 工作。在第一个 await 语句之前的 all 代码肯定会出现这种情况。这项工作将在“主”线程(调用insert的线程)上执行-这将是您的瓶颈或至少是瓶颈的一部分,因为您调用@时该部分代码的并行度将为1 987654332@在一个紧密的循环中,不管你是否await产生的任务。

为了说明上述观点,请考虑以下示例:

void Test()
{
    Debug.Print($"Kicking off async chain (thread {Thread.CurrentThread.ManagedThreadId}) - this is the main thread");
    OuterTask().Wait(); // Do not block on Tasks - educational purposes only.
}

async Task OuterTask()
{
    Debug.Print($"OuterTask before await (thread {Thread.CurrentThread.ManagedThreadId})");
    await InnerTask().ConfigureAwait(false);
    Debug.Print($"OuterTask after await (thread {Thread.CurrentThread.ManagedThreadId})");
}

async Task InnerTask()
{
    Debug.Print($"InnerTask before await (thread {Thread.CurrentThread.ManagedThreadId})");
    await Task.Delay(10).ConfigureAwait(false);
    Debug.Print($"InnerTask after await (thread {Thread.CurrentThread.ManagedThreadId}) - we are now on the thread pool");
}

这会产生以下输出:

启动异步链(线程 6) - 这是主线程 等待之前的 OuterTask(线程 6) 等待之前的 InnerTask(线程 6) 等待后的 InnerTask(线程 8)——我们现在在线程池中 等待后的 OuterTask(线程 8)

请注意,Task1 中第一个 await 之前的代码甚至 Task2 仍然在“主”线程上执行。我们的链实际上是在启动外部任务的同一线程上同步执行的,直到我们 await 第一个真正的异步操作(在本例中为 Task.Delay)。

另外

如果您在SynchronizationContext.Current 不为空的环境中运行(即Windows 窗体、WPF),并且您没有在insert 方法中等待的任务上使用ConfigureAwait(false),则由异步计划的继续状态机之后第一个await 语句可能在“主”线程上执行 - 尽管在某些环境(即 ASP.NET)中不能保证这一点。

解释,第 2 部分(在线程池上执行 Tasks)

如果作为insert 方法的一部分,您选择手动启动任何Tasks,那么您很可能使用Task.Run任何其他 启动未指定TaskCreationOptions.LongRunning 的新任务的方法。一旦线程池饱和,任何新启动的任务都会排队,从而降低并行系统的吞吐量。

证明:

IEnumerable<Task> tasks = Enumerable
    .Range(0, 200)
    .Select(_ => Task.Run(() => Thread.Sleep(100))); // Using Thread.Sleep to simulate blocking calls.

await Task.WhenAll(tasks); // Completes in 2+ seconds.

现在TaskCreationOptions.LongRunning

IEnumerable<Task> tasks = Enumerable
    .Range(0, 200)
    .Select(_ => Task.Factory.StartNew(
        () => Thread.Sleep(100), TaskCreationOptions.LongRunning
    ));

await Task.WhenAll(tasks); // Completes in under 130 milliseconds.

生成 200 个线程通常不是一个好主意(这不会很好地扩展),但如果阻塞调用的大规模并行化是绝对要求,上面的 sn-p 向您展示了一种方法用 TPL 来做。

【讨论】:

  • 谢谢,你的回复真的很有帮助
【解决方案2】:

在第一个示例中,您手动创建了线程。其次,您创建了任务。任务 - 可能 - 正在使用线程池,其中存在有限的线程数。因此,大多数任务 ae 在队列中等待,而其中少数任务在可用线程上并行执行。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2012-04-28
  • 2020-08-22
  • 2020-11-02
  • 1970-01-01
  • 2016-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多