我最近遇到过IAsyncResult并且玩了很长时间。我真正想知道的是,当我们有更好的替代ThreadPool时,为什么要使用IAsyncResult?根据我目前对它们的理解,我会选择在几乎所有情况下都使用ThreadPool。所以我的问题是,是否存在IAsyncResult优于另一个的上下文?
为什么我不喜欢IAsyncResult:
将其放入代码:
线程池
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" );
}
答案 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方法。