【问题标题】:Async looping in c#c#中的异步循环
【发布时间】:2020-09-04 07:37:19
【问题描述】:

我想了解异步在 c# 中的工作原理,并找到了一些基于 Task.Delay 的基本示例。它们工作正常,这样的代码可以按预期工作:

static int taskNo = 0;

static async Task<Task> test(int k)
{
    int _taskNo = ++taskNo;
    Console.WriteLine($"Started task {_taskNo}");
    await Task.Delay(1000 * k);
    Console.WriteLine($"Finished task {_taskNo}");
    return Task.CompletedTask;
}

static async Task Main(string[] args)
{
    Task[] tasks = new Task[3];

    tasks[0] = test(4);
    tasks[1] = test(3);
    tasks[2] = test(2);

    await Task.WhenAll(tasks);
    Console.WriteLine("All tasks finished.");
    Console.ReadLine();
}

最初我想尝试制作一些大循环,例如添加数千次字符串,只是为了尝试一下。是否可以异步执行?当我尝试使用这样的代码时,它只能在一个线程上工作(我可以看到只有一个线程在 100% 工作)。如何创建该循环的多个实例以在 CPU 的每个线程上工作?

//how to make this code to be able to work asynchronously?
static async Task LoopTest()
{
    int _taskNo = ++taskNo;
    Console.WriteLine($"Started task {_taskNo}");

    string s = "";
    for(int i = 0; i < 1000000; i++)
    {
        s += "qwertyuzxcvb";
    }

    Console.WriteLine($"Finished task {_taskNo}");
}

编辑: 在其中一个 cmets 中,我询问了一个线程如何比多个线程更快地执行操作,这是我测试的代码:

        static int taskNo = 0;

        static async Task LoopTest()
        {
            int _taskNo = ++taskNo;
            Console.WriteLine($"Started task {_taskNo}");

            string s = "";
            for(int i = 0; i < 30000; i++)
            {
                s += "qwertyuzxcvb";
            }

            Console.WriteLine($"Finished task {_taskNo}");
        }

        static async Task Main(string[] args)
        {
            DateTime asyncStartTime = DateTime.Now;
            Console.WriteLine($"Started async.");

            // async

            Task[] tasks = new Task[15];

            Parallel.ForEach(tasks, (Task) => LoopTest());
            
            DateTime asyncEndTime = DateTime.Now;

            taskNo = 0;
            //sync
            DateTime syncStartTime = DateTime.Now;
            Console.WriteLine($"Started sync.");

            for(int i = 0; i < tasks.Length; i++)
            {
                LoopTest();
            }

            DateTime syncEndTime = DateTime.Now;

            TimeSpan asyncTimeSpan = asyncEndTime - asyncStartTime;
            TimeSpan syncTimeSpan = syncEndTime - syncStartTime;

            Console.WriteLine($"Async finished in {asyncTimeSpan}, sync finished in {syncTimeSpan}.");
            Console.ReadLine();
        }

我可以看到,当异步启动时,所有线程都被固定到 100%(这是 i7 9750H,12 个线程正在工作,它说 CPU 使用率为 100%),一旦它开始同步,只有一个线程被固定到100%,整体 CPU 使用率为 25%。也许只是有一些愚蠢的错误,但是如何才能更快地同步呢?

结果:

顺便说一句,我知道这是一个非常愚蠢的异步示例,而且我知道在其他情况下有很多好处,但我只是想做那个循环测试,现在我有越来越多的问题......

【问题讨论】:

  • 为了您的测试目的,您应该使用Task.Run() 来开始新任务。
  • Task.Run(() =&gt; my_hard_work_method());
  • 您使用错误的示例来查看异步的好处。对于 splittin 计算操作,请使用 Parallel.ForEach,这将消耗多个处理器内核。要同时访问外部资源(数据库、文件系统、Web 服务或其他),请使用异步方法。 IO 操作的异步方法将只使用一个线程,但它同时等待来自不同资源的响应。
  • @Colinovsky 我测试了你的代码,结果是Async finished in 00:00:00.4990435, sync finished in 00:00:00.5686410.。所以正如预期的那样,parallel 版本要快一些。请注意,在您的代码中 nothing 是异步的,这是因为您没有使用任何 true 异步方法并等待它的任务。但也请注意,在一般情况下 async/await 并不是并行化代码的正确工具。您可能需要在输出中添加on {Thread.CurrentThread.ManagedThreadId},以查看在哪个线程上执行了LoopTest 调用。
  • 术语异步在这里不适用。您可能希望实现并行性。您可能需要检查this 问题,以解决问题。

标签: c# .net loops asynchronous async-await


【解决方案1】:

可以异步吗?

连接字符串或递增整数没有什么异步的。异步用于在等待 I/O 时释放线程。

您可以使用Task.Run() 通过卸载到 ThreadPool 来伪造异步方法,但这仅适用于需要保持畅通的 UI 线程;它增加了开销,并且仍然导致使用线程来完成工作。

不管怎样,LoopTest 不是一个异步方法,所以不要试图强迫它成为一个。

如果您确实有一个 UI 线程,只需像这样调用它:

 await Task.Run(LoopTest);

现在 UI 线程被释放,直到 LoopTest 完成。

【讨论】:

  • 异步不仅仅用于IO操作,繁重的计算可以拆分到多个线程中。
  • @Fabio 这不是真正的异步;这是并行性。您可以使用async / await 来实现并行性,但Parallel 类更适合/针对该目的进行了优化。
猜你喜欢
  • 2017-05-02
  • 2018-09-18
  • 2018-10-17
  • 2017-09-30
  • 2020-03-07
  • 1970-01-01
  • 2011-05-16
  • 2015-02-27
  • 1970-01-01
相关资源
最近更新 更多