方法循环的异步编程

Asynchonous programming for method loops

我正在尝试学习异步编程以及如何从中受益。

我希望每当我循环执行需要很长时间才能完成的方法时,我都可以使用它来提高性能,例如以下方法。

string AddStrings()
{
    string result = "";

    for (int i = 0; i < 10000; i++)
    {
        result += "hi";
    }

    return result;
}

显然这个方法没有太大的价值,我特意弄低了效率,为了测试异步编程的好处。测试是通过循环方法 100 次来完成的,首先是同步的,然后是异步的。

Stopwatch watch = new Stopwatch();
watch.Start();

List<string> results = WorkSync();
//List<string> results = await WorkAsyncParallel();

watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);

List<string> WorkSync()
{
    var stringList = new List<string>();

    for (int i = 0; i < 100; i++)
    {
        stringList.Add(AddStrings());
    }

    return stringList;
}

async Task<List<string>> WorkAsyncParallel()
{
    var taskList = new List<Task<string>>();

    for (int i = 0; i < 100; i++)
    {
        taskList.Add(Task.Run(() => AddStrings()));
    }

    var results = (await Task.WhenAll(taskList)).ToList();
    return results;
}

超级乐观(天真),我希望异步循环的速度是同步循环的 100 倍,因为所有任务都是同时 运行。虽然事实并非如此,但循环时间减少了三分之二以上,从大约 5000 毫秒减少到 1500 毫秒!

现在我的问题是:

  1. 是什么让异步循环比同步循环快,但不是快将近 100 倍?我猜这 100 个任务中的每一个都在争夺有限的 CPU?
  2. 这是提高循环方法性能的有效方法吗?

提前致谢。

My hope is that I can use it to improve performance

不是真的。并发(并行或异步)可以 提高性能,但异步代码本身更多的是释放线程。异步代码有两个主要好处:

  1. Server-side 应用程序获得更好的可扩展性。通过使用 更少的 线程,异步代码可以比同步代码扩展得更远、速度更快。
  2. UI 应用获得更好的响应能力。通过释放 UI 线程,异步代码提供了更好的用户体验。

这些都与性能无关。例如,将同步 server-side 请求处理程序与其基本的异步对应项进行比较时,异步请求处理程序通常 较慢 (略微),但服务器作为一个整体可扩展性更好。

也就是说,异步代码可以实现自然并发(例如,Task.WhenAll)。如果您最终确实添加了并发性,那么您 可以 看到一些性能优势 - 有时是非常显着的。

The test is done by looping over the method 100 times, first synchronously and then asynchronously.

从技术上讲,您在此处比较 single-threaded 和并行代码。

正如 Jeroen 在评论中指出的那样,如果您遇到并行问题,Parallel 或 PLINQ 是合适的工具。虽然您 可以 使用 Task.Run,但它是一个非常 low-level 的并行编程工具。 Task.Run更常用于“将一个东西推到线程池”,而不是“将这些100东西推到线程游泳池”。

What makes the asynchronous loop faster than the synchronous loop, but not nearly 100 times faster? I'm guessing each of the 100 tasks are fighthing for a limited amount of CPU?

是的。现在大多数机器都有 multi-core CPUs,每个核心一次只能做一件事。所以如果你有 4 或 8 个核心,那就是你的并行度的极限。

Is this a valid method to improve performance when looping methods?

如果你有很多工作要做并行,那么并行编程是可以接受的。同样,我建议使用 higher-level 构造,如 Parallel 或 PLINQ,它们具有更好的 built-in 分区策略和其他优化,这比在线程池中扔一堆任务更有效。

并行编程确实有其注意事项:

  • 如果您的工作量太大 fine-grained,那么并行工作最终可能会 变慢 ,因为分区、排队和调度的开销抵消了并发带来的好处.
  • 您通常希望在某些情况下避免并行,例如在服务器端处理请求。允许一个请求消耗整个服务器的所有 CPU 资源通常不是一个好主意。

现在,如果您想测试 异步 的好处,那么我建议使用本质上异步的操作(例如,客户端 Web 请求)。比如说,如果您的代码访问了 100 个 URL。那将是不需要线程池线程的更自然的异步。