异步等待性能 - 直接方法调用与任务包装器调用

时间:2014-10-04 10:54:50

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

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

sumProgram.CallSum(i, i + 1);

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

我最初的理解是Task API会使它大大加快,但与我的期望相反,这肯定反映了我的理解不足,直接调用在性能方面要好得多。实际上,当在GetSum方法中引入额外的线程休眠时,它似乎只会影响任务调用,而且也会影响很大。

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

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

  • 我得到了令人费解的结果,因为对于直接调用,秒表将在所有执行完成之前发布时间,而且由于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);
        }
    }
    

3 个答案:

答案 0 :(得分:5)

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与速度无关;它是关于释放当前的线程。

答案 1 :(得分:2)

上述评论表明,您在任务中完成的工作太少,无法超过设置成本。

此外,Tasks和async-await不适用于直接调用的情况。制作异步并不会让它变得更快。它们适用于以下情况:您可以保持调用线程运行/执行其他操作,同时等待冗长/可用/可取消的任务以硬件实际并行化的方式完成或分配跨核心的足够工作。并且没有丑陋的,由内而外的代码来完成所有这些,这些代码是异步编程的特征。

你的实验很简单,是错误的。您所测量的是在无法获胜的情况下无意中使用.NET中的任务机制。

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

答案 2 :(得分:1)

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

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