首页 > Python基础教程 >
-
C#线程 入门(6)
EndInvoke做三件事。首先,它会等待异步委托完成执行(如果尚未执行)。其次,它接收返回值(以及任何ref或out参数)。第三,它将所有未处理的工作程序异常抛出回调用线程。
如果您使用异步委托调用的方法没有返回值,则仍然(在技术上)有义务调用EndInvoke。实际上,这是有争议的。没有EndInvoke警察对违规者进行处罚!但是,如果您选择不调用EndInvoke,则需要考虑worker方法上的异常处理,以避免无提示的失败。
您还可以在调用BeginInvoke时指定一个回调委托-一种接受IAsyncResult对象的方法,该方法在完成后会自动调用。这允许煽动线程“忘记”异步委托,但是在回调端需要一些额外的工作:
static void Main() { Func<string, int> method = Work; method.BeginInvoke ("test", Done, method); // ... // } static int Work (string s) { return s.Length; } static void Done (IAsyncResult cookie) { var target = (Func<string, int>) cookie.AsyncState; int result = target.EndInvoke (cookie); Console.WriteLine ("String length is: " + result); }
BeginInvoke的最后一个参数是填充IAsyncResult的AsyncState属性的用户状态对象。它可以包含您喜欢的任何内容;在这种情况下,我们使用它将方法委托传递给完成回调,因此我们可以在其上调用EndInvoke。
优化线程池
线程池从其池中的一个线程开始。分配任务后,池管理器会“注入”新线程以应对额外的并发工作负载(最大限制)。在足够长时间的不活动之后,如果池管理器怀疑这样做会导致更好的吞吐量,则可以“退出”线程。
您可以通过调用ThreadPool.SetMaxThreads;来设置池将创建的线程的上限。默认值为:
- 32位环境中Framework 4.0中的1023
- 在64位环境中的Framework 4.0中为32768
- 框架3.5中的每个核心250个
- Framework 2.0中每个内核25个
(这些数字可能会因硬件和操作系统而异。)之所以有很多原因,是为了确保某些线程被阻塞(在等待某种条件(例如,来自远程计算机的响应)时处于空闲状态)的进度。
您还可以通过调用ThreadPool.SetMinThreads设置下限。下限的作用是微妙的:这是一种高级优化技术,它指示池管理器在达到下限之前不要延迟线程的分配。当线程被阻塞时,提高最小线程数可提高并发性(请参见侧栏)。
默认的下限是每个处理器内核一个线程-允许全部CPU利用率的最小值。但是,在服务器环境(例如IIS下的ASP.NET)上,下限通常要高得多-多达50个或更多。
最小线程数如何工作?
实际上,将线程池的最小线程数增加到x并不会实际上强制立即创建x个线程-线程仅根据需要创建。相反,它指示池管理器在需要它们时立即最多创建x个线程。那么,问题是,为什么在需要时线程池会延迟创建线程的时间呢?
答案是防止短暂的短暂活动导致线程的完全分配,从而突然膨胀应用程序的内存空间。为了说明这一点,请考虑运行一个客户端应用程序的四核计算机,该应用程序一次可处理40个任务。如果每个任务执行10毫秒的计算,则假设工作在四个核心之间分配,整个任务将在100毫秒内结束。理想情况下,我们希望40个任务恰好在四个线程上运行:
- 减少一点,我们就不会充分利用这四个核心。
- 再有,我们将浪费内存和CPU时间来创建不必要的线程。
这正是线程池的工作方式。只要将线程数与内核数进行匹配,只要有效地使用了线程(在这种情况下就是这样),程序就可以在不影响性能的情况下保留较小的内存占用。
但是现在假设,每个任务而不是工作10毫秒,而是查询Internet,在本地CPU空闲时等待半秒以响应。池管理器的线程经济策略崩溃了;现在创建更多线程会更好,因此所有Internet查询都可以同时发生。
幸运的是,池管理器有一个备份计划。如果其队列保持静止状态超过半秒,它将通过创建更多线程(每半秒一个)来响应,直至达到线程池的容量。
延迟的半秒是一把两刃剑。一方面,这意味着一次短暂的短暂活动不会使程序突然消耗掉不必要的40 MB(或更多)内存。另一方面,当池中的线程阻塞时,例如查询数据库或调用WebClient.DownloadFile时,它可能不必要地延迟事情。因此,可以通过调用SetMinThreads来告诉池管理器不要延迟前x个线程的分配:
ThreadPool.SetMinThreads(50,50);
(第二个值指示要分配给I / O完成端口的线程数,由APM使用,具体请参见C#4.0第23章的内容。)
默认值为每个内核一个线程。