IAsyncResult与ThreadPool

时间:2014-01-22 09:14:14

标签: c# threadpool iasyncresult

我最近遇到过IAsyncResult并且玩了很长时间。我真正想知道的是,当我们有更好的替代ThreadPool时,为什么要使用IAsyncResult?根据我目前对它们的理解,我会选择在几乎所有情况下都使用ThreadPool。所以我的问题是,是否存在IAsyncResult优于另一个的上下文?

为什么我不喜欢IAsyncResult:

  • 增加了BeginXXX和EndXXX的复杂性
  • 如果他不关心返回值,来电者可能忘记致电EndXXX
  • 增加了API设计的冗余(我们需要创建Begin和End包装器方法 对于我们想要异步运行的每个方法)
  • 可读性降低

将其放入代码:

线程池

  public void ThreadPoolApproach()
  {
     ThreadPool.QueueUserWorkItem( ( a ) =>
     {
        WebClient wc = new WebClient();
        var response = wc.DownloadString( "http://www.test.com" );
        Console.WriteLine( response );
     } );
  }

IAsyncResult的

  public void IAsyncResultApproach()
  {
     var a = BeginReadFromWeb( ( result ) =>
     {
        var response = EndReadFromWeb( result );
        Console.WriteLine( response );
     }, "http://www.test.com" );
  }

  public IAsyncResult BeginReadFromWeb( AsyncCallback a, string url )
  {
     var result = new AsyncResult<string>( a, null, this, "ReadFromFile" );

     ThreadPool.QueueUserWorkItem( ( b ) =>
     {
        WebClient wc = new WebClient();
        result.SetResult( wc.DownloadString( url ) );
        result.Complete( null );
     } );

     return result;
  }

  public string EndReadFromWeb( IAsyncResult result )
  {
     return AsyncResult<string>.End( result, this, "ReadFromFile" );
  }

3 个答案:

答案 0 :(得分:17)

不,你的两个代码片段之间存在巨大的差异。事实上两者都使用线程池,第一个当然是明确地使用它。第二个以不太明显(和破坏)的方式执行它,IAsyncResult回调在线程池线程上执行。

线程池是一个共享资源,在大型程序中,您将有许多TP线程用途。不仅在您自己的代码中明确地,.NET Framework也使用它们。在线程池上运行的代码类型的指导是它是快速执行的代码,并且不会进行任何阻塞调用,使TP线程进入等待状态。阻塞以非常低效的方式使用非常昂贵的操作资源,并且使用可能正在使用TP线程的其他代码。线程池的一个重要部分是调度程序,它尝试将执行 TP线程的数量限制为机器可用的CPU核心数。

但是阻塞正是你在第一个片段中所做的。 WebClient.DownloadString()是一种非常慢的方法,它无法比您的Internet连接或线路另一端的服务器允许更快地完成。实际上,您正在占用TP线程,可能分钟。根本没有做任何工作,它总是在等待Socket.Read()调用完成。有效的CPU核心利用率最多只有几个百分点。

使用BeginXxxx()或XxxxAsync()方法时,不同。它在内部实现为一些代码,要求操作系统启动I / O操作。只需几微秒。操作系统将请求传递给设备驱动程序,如果是DownloadStringAsync(),则将TCP / IP堆栈传递给设备驱动程序。它将作为I / O请求队列中的数据项。你的电话很快就会回来。

最终,您的网卡从服务器获取数据,驱动程序完成I / O请求。通过几个层,使CLR获取另一个TP线程并运行您的回调。您可以快速对数据执行任何操作,某些处理步骤通常也需要几微秒。

请注意区别,您的第一个代码占用分钟的TP线程,异步版本占用微秒的线程。异步版扩展更好,能够处理许多 I / O请求。

代码异步版本的一个重要问题是正确编写要困难得多。同步版本中的局部变量需要成为异步版本中的类的字段。调试起来也困难得多。这就是.NET获得Task类的原因,后来通过支持C#和VB.NET语言中的async / await关键字进一步扩展。

答案 1 :(得分:6)

让我们抛开自然异步的IO绑定操作,这些操作不需要专用线程来完成(参见Stephen Cleary的There Is No Thread)。在池线程上执行自然异步 DownloadStringAsync API的同步版本DownloadString没有多大意义,因为阻塞了宝贵的资源是徒劳的:线程。

相反,让我们专注于CPU绑定的计算操作,这需要一个专用的线程。

首先, .NET Framework中没有标准的AsyncResult<T>类。我相信,您在代码中引用的AsyncResult<string>的实现正在进行中来自杰弗里里希特的Concurrent Affairs: Implementing the CLR Asynchronous Programming Model文章。我还相信作者展示了如何实现AsyncResult<T> 用于教育目的,说明了CLR实现的外观。他通过ThreadPool.QueueUserWorkItem在池线程上执行一项工作,并实现IAsyncResult以提供完成通知。有关详细信息,请参阅文章随附的LongTask.cs

所以,回答这个问题:

  

我真正想知道的是为什么在我们有办法的时候使用IAsyncResult   那里有更好的替代ThreadPool吗?

这不是“IAsyncResult vs ThreadPool”案例。相反,在您的问题的上下文中, IAsyncResult是{ {1}} ,它提供了一种通知调用者工作项已完成执行的方法。 ThreadPool.QueueUserWorkItem API本身没有此功能,它只返回ThreadPool.QueueUserWorkItem,指示工作项是否已成功排队以便在池线程上进行异步执行。

但是,对于这种情况,您根本不需要实施bool 或使用AsyncResult<T>框架允许在ThreadPool.QueueUserWorkItem 上异步执行委托,并通过使用委托的BeginInvoke方法跟踪完成状态。这就是框架如何为代表实现Asynchronous Programming Model (APM) pattern。例如,以下是使用ThreadPool执行某些CPU绑定工作的方法:

BeginInvoke

最后,值得一提的是,APM模式正在被更方便,结构更合理的Task-based Asynchronous Pattern (TAP)所取代。 recommended TAP模式应该优于其他更低级别的API。

答案 2 :(得分:0)

基本上这两种方式只是同一事物的不同行为。在ThreadPool上使用IAsyncResult的原因之一是返回值:Threading.WaitCallback返回void,因此您不能通过ThreadPool.QueueUserWorkItem调用直接返回任何值,但您可以使用IAsyncResult方法。