【问题标题】:OK to do heavy processing in async callbacks?可以在异步回调中进行繁重的处理吗?
【发布时间】:2011-01-11 03:20:06
【问题描述】:

是否可以在 .NET 的异步回调中进行繁重的处理,在返回之前将它们占用几秒钟?还是我剥夺了操作系统/运行时的重要资源?

例如,考虑TcpListener.BeginAcceptSocket。我的回调从调用EndAcceptSocket 开始,然后花一段时间接收数据,然后才关闭套接字并返回。这是它的本意使用方式,还是我希望在我自己的线程上进行额外的处理?

【问题讨论】:

标签: .net asynchronous iasyncresult


【解决方案1】:

是的,这就是异步套接字(Tcp 客户端、侦听器等)的使用方式。您应该始终确保调用 end aysnc 方法,然后执行您想要的任何处理。不调用 EndAccept()、EndSEnd()、EndReceive() 等方法可能会导致内存泄漏,因此始终是一个好习惯。

所使用的线程与您自己手动设置后台线程没有什么不同,并且实际上设计用于几秒钟的“长期操作”。相信我,您不希望在调度或 GUI 线程上运行任何需要很长时间的东西。

我有超过 90 个基于移动的系统,它们使用异步套接字与服务器通信,它做得非常好:比 Web 服务快得多(记住所有 Web 协议都在 Socket 之上运行),易于检测错误,等等等等。

我在我的服务器代码上做同样的事情(与其他一些用于中间件和后端通信的 WCF 混合在一起),它是我们使用过的最具可扩展性的通信。您必须在 Google 上进行搜索,但有人发布了他使用该技术进行的测试,他能够支持 1,000 个并发通信到只有 11 个线程的服务器。还不错。

来自 MS 的服务器示例:http://msdn.microsoft.com/en-us/library/fx6588te.aspx

来自 MS 的客户端示例:http://msdn.microsoft.com/en-us/library/bew39x2a.aspx

要“完美”它需要更多的东西,但这是一个很好的起点。

【讨论】:

  • codeproject.com/KB/threads/threadtests.aspx 的文章似乎表明,如果您在线程池线程中进行长时间处理(例如异步调用完成处理程序),.NET 线程池会出现性能问题。您没有发现这个问题吗?
  • 我不能说处理时间真的很长,但从 Socket 通信的角度来看,我们从来没有遇到过任何问题。我们所有的通信都遵循缓冲异步方法,甚至是大传输……比如说一个数据,在移动平台上可能需要很长时间,只要网络是可靠的,Socket 就被证明是可靠的。具有讽刺意味的是,几乎所有通信网络技术都使用更高抽象层下的 Sockets 来处理繁重的工作(但通常在移动设备中不可用)。
  • @Sander:早在 2003 年,ThreadPool 的每个 CPU 中默认有 25 个线程(大多数计算机都是单核的)。现在默认值是每 cpu 250,所以问题比以前少了:msdn.microsoft.com/en-us/library/…(.net 4 在 32 位上达到每 cpu 1023,在 64 位上达到每 cpu 32768!)
  • @Matthew:不过,这些只是最大数字——线程池需要时间来增长。我在 CodeProject 文章中重复了这个实验,并且确实注意到了这种情况下的一个问题。我会在这个问题中发布我的结果作为答案。
  • @Sander:根据 Campbell、Johnson、Miller 和 Toub 撰写的“使用 Microsoft.NET 进行并行编程”,ThreadPool 的线程注入算法以每秒 2 个的速度添加额外线程。我不确定这是否可以调整或与通信 API 使用的值相同,但知道这一点很有趣。我有一个使用我的基于 Asych 套接字的通信引擎的服务器,它在处理 2,200 个并发用户方面做得很好。我目前正在实施一个拥有 50,000 个用户的系统(64 位操作系统上的双四核机箱)。
【解决方案2】:

我重复了CodeProject article on this topic 的实验,发现 .NET 4 的结果与 2003 年描述的结果相似。请注意,这篇文章实际上并未列出有问题部分的结果,但据我了解主要问题仍然存在。

我重复使用了 CodeProject 文章中的代码 - 只需下载它即可自己运行此测试或进行实验。

测试将尝试使用 10 个并行线程在 1 秒内尽可能多地计数。

使用 10 个后台线程(即new Thread()

T0 = 4451756 T1 = 4215159 T2 = 5449189 T3 = 6244135 T4 = 3297895 T5 = 5302370 T6 = 5256763 T7 = 3779166 T8 = 6309599 T9 = 6236041 总计 = 50542073

使用 10 个线程池工作项

T0 = 23335890 T1 = 20998989 T2 = 22920781 T3 = 9802624 T4 = 0 T5 = 0 T6 = 0 T7 = 0 T8 = 0 T9 = 0 总计 = 77058284

请注意,10 个线程池工作项中只有 4 个线程池工作项在 1 秒时间片内实际执行过!这是在四核 CPU 上,因此每个内核一个线程。前四个任务完成后执行的其他任务,因为分配的 1 秒已经过期,所以它们没有增加它们的计数器。

这里的结论:对于长任务,ThreadPool 会使一些任务在其他任务之后等待! 因此,我强烈建议不要在 ThreadPool 任务中进行任何长时间处理(例如异步完成处理程序)。否则,如果您的数据处理占用 CPU,您可能会阻止更重要的异步调用完成,或者如果只有某些任务执行大量处理,您可能会出现非常不稳定的性能。

使用文章中的自定义 ThreadPool 实现

T0 = 7175934 T1 = 6983639 T2 = 5306292 T3 = 5078502 T4 = 3279956 T5 = 8116320 T6 = 3262403 T7 = 7678457 T8 = 8946761 T9 = 8500619 总计 = 64328883

【讨论】:

  • 您从结果中遗漏的是 ThreadPool 版本的性能明显优于非线程池版本约 50%!这篇文章说使用 ThreadPool 的速度要慢得多,但现在已经不是这样了。与编程中的所有内容一样,尽管归结为“取决于”。如果您的任务不需要立即开始执行,那么您可以使用线程池,由于上下文切换较少,它们会更快地完成,否则您可以自己管理。
  • @Matthew:是的,我同意 ThreadPool 在这里计数更多,但我不同意原因。 ThreadPool 计数较多的原因是四个 ThreadPool 线程已经存在,而手动创建新线程时,线程实际启动需要大量时间。所以我会推翻您的结论:如果您的任务不需要立即执行,请使用新线程;如果您需要立即执行,请使用 ThreadPool(并且不要将其用于长任务)。
猜你喜欢
  • 1970-01-01
  • 2018-12-16
  • 2016-08-08
  • 1970-01-01
  • 1970-01-01
  • 2015-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多