【问题标题】:Async Await performance - Direct method call vs Task wrapper call异步等待性能 - 直接方法调用与任务包装器调用
【发布时间】:2014-10-04 10:54:50
【问题描述】:

我创建的一个小程序,用于了解 Async Await 的工作并在 for 循环中调用 async 方法,作为直接方法调用:

sumProgram.CallSum(i, i + 1);

或使用任务 API Task.Run / Task.Factory.StartNew

我最初的理解是 Task API 会大大提高速度,但与我的预期相反,这肯定反映了我的理解不足,直接调用在性能方面要好得多。事实上,当在 GetSum 方法中引入额外的 Thread sleep 时,它似乎只影响 Task 调用,而且影响很大。

现在我了解直接调用的第一部分更快,因为它们是异步执行的,并且没有添加到任务列表并使它们等待的开销,但是当我将相同的技术转移到真正的编程范式时,那么问题仍然存在:

  • 对于直接调用,没有什么可以模拟Task.WaitAll,所以他们会退出调用方法,即使所有的执行都没有完成,所以我唯一的选择是Task wrapper。

  • 我是否得到了令人费解的结果,因为对于直接调用,秒表甚至会在所有执行完成之前发布时间,而由于 waitAll,任务包装器不会出现这种情况

  • 为了执行这个程序,您需要注释/取消注释相关部分以获得正确的结果

     class Program
        {
            static void Main(string[] args)
            {
                Program sumProgram = new Program();
    
            List<Task> taskList = new List<Task>(); // Only For Task
    
            Stopwatch sw = Stopwatch.StartNew();
    
            for (int i = 0; i < 100000; i++)
            {
                taskList.Add(Task.Factory.StartNew(() => { sumProgram.CallSum(i, i + 1); })); // For Task use one 
                taskList.Add(Task.Run(() => { sumProgram.CallSum(i, i + 1); })); // For Task use one 
                sumProgram.CallSum(i, i + 1);
            }
    
            Task.WaitAll(taskList.ToArray()); // Only For Task
    
            sw.Stop();
            Console.WriteLine("Time Elapsed :: " + sw.ElapsedMilliseconds);
        }
    
        public async Task CallSum(int num1, int num2)
        {
            Func<int> callFunc = (() =>
            {
                return GetSum(num1, num2);
            });
    
            int result = await Task.Run<int>(callFunc);
            //Console.WriteLine("Sum Result :: " + result);
        }
    
        public int GetSum(int num1, int num2)
        {
            Thread.Sleep(10);
            return (num1 + num2);
        }
    }
    

【问题讨论】:

  • 任务创建和处理的开销很大。这些任务确实太少了,不重要。
  • 你的问题是什么?
  • 问题在要点中需要重申 - 在实际编程场景中,似乎没有办法等待异步执行直接调用,所以等待所有的任务似乎是在所有异步之前保持方法的唯一方法处决返回,我还有其他选择吗
  • @Erno de Weerd 我理解你的观点,但是在真正的编程范式中,每个任务都会做一个密集的操作,我的例子是相当简化的表示

标签: c# .net asynchronous task-parallel-library task


【解决方案1】:

async != "更快"

事实上,async 代码几乎总是(稍微)慢。

那么,为什么要使用async

在客户端 (UI) 应用程序中,async 允许您保持对用户的响应。对比这段代码:

void Button_Click()
{
  Thread.Sleep(10000);
}

使用此代码:

async void Button_Click()
{
  await Task.Delay(10000);
}

在服务器端(例如 ASP.NET),async 允许您的代码使用更少的线程来处理更多的请求。因此,提高了可扩展性。

请注意,在这两种情况下,async 代码实际上比其同步对应代码。对于 UI,休眠当前线程比创建和启动计时器、创建和等待任务,然后在计时器触发时恢复异步方法要快。对于 ASP.NET 来说,阻塞当前线程比为异步操作创建一个任务,然后在异步方法恢复时将请求上下文切换到另一个线程要快。

不过,async 带来了其他好处。对于 UI,延迟期间不会阻塞 UI 线程。对于 ASP.NET,请求线程可以在异步操作进行时处理其他请求。

async 与速度无关;它是关于释放当前线程。

【讨论】:

  • 感谢您提供的重要信息,我能够理解我对 conceot 的理解中的缺陷
【解决方案2】:

上面的 cmets 说您在 Task 中完成的工作太少,不足以超过它的设置成本。

此外,Tasks 和 async-await 并不适用于直接调用很容易的场景。使某些东西异步并不能使它更快。它们适用于在等待冗长/失败/可取消的任务以硬件实际并行化的方式完成或跨内核分配足够工作的同时保持调用线程运行/执行其他操作的场景。并且在没有异步编程特征的丑陋、由内而外的代码的情况下完成这一切。

很简单,您的实验是错误的。您所测量的是在 .NET 中使用 Task 机制的情况,它不可能获胜。

试试这个。在每个任务中,将一些内存从一个位置复制到另一个位置(使用 Marshal.AllocHGlobal 分配并使用 P/Invoke CopyMemory 进行复制)。一旦达到临界阈值,您就会看到这种机制与单线程副本相比要快多少。此外,使用 async-await 您的代码会看起来更好。

【讨论】:

  • 完全同意,我知道建议的任务需要做很多密集的工作,任务才能显示出好处。样本不匹配,但我的问题略有不同。在实际程序中,即使我有一个更快的直接调用,但似乎没有办法阻止执行,除了使用任务之外我没有其他选择。在实际场景中,每个任务都会进行密集的数据库调用,需要并行执行
【解决方案3】:

您的代码的最大问题是您正在使用的选项节点正在等待CallSum() 完成。为此,请获取CallSum() 返回的Task 并等待它。例如:

taskList.Add(sumProgram.CallSum(i, i + 1));

【讨论】:

  • 感谢您的更正,我已经意识到这个问题,并且它似乎在更改后表现更好。直接调用当然不是解决方案,因为我不能等待异步任务完成。你认为 Parallel api 在这种情况下会表现得更好吗,因为我确实需要等待 Parallel / Async 任务完成
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-09
  • 1970-01-01
  • 2018-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多