BeginRead&EndRead
那么,既然有IO完成端口这么个好东西,如是有很多人想在.NET里也利用利用。其实大可不必,在.NET里异步的IO内部就是使用了IO完成端口。每个CLR初始化后都会创建一个IO完成端口,用来处理IO请求。很多人应该知道ThreadPool里的线程分为两类:worker thread和io completion thread,这里的io completion thread就是上一节说的跟IO完成端口相关联的那些thread。要说它跟其他的thread有什么不同?没什么不同,只是受IO完成端口控制而已。
为了看看在.NET中是如何利用IO完成端口的,我们将FileStream.BeginRead作为我们的入口点。在FileStream的Init方法里我们会看到这么一段代码:
{
//...
try
{
flag4 = ThreadPool.BindHandle(this._handle);
}
finally
{
CodeAccessPermission.RevertAssert();
}
//...
}
我们感兴趣的就是ThreadPool.BindHandle。还记得上面对IO完成端口的描述么?其实这里做的事儿就是将该文件句柄与每个CLR都初始化了的那个IO完成端口绑定。也就是说如果我们创建一个FileStream时指定了异步,那么IO完成端口就会“监视”这个文件。
我们再来看看BeginRead这个方法。该方法是用来发起异步IO请求的方法,该方法执行后会立即返回,不阻塞线程。
首先,看这么段代码:
{
return base.BeginRead(array, offset, numBytes, userCallback, stateObject);
}
也就是说如果我们创建FileStream时,没有指定为异步,就会调用基类的BeginRead方法,那基类的这个方法又是如何实现的呢?
public virtual IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
//...
ReadDelegate delegate2 = new ReadDelegate(this.Read);
//...
return delegate2.BeginInvoke(buffer, offset, count, callback, state);
}
其实是创建一个调用同步的Read方法的委托,然后调用一下BeginInvoke方法(在第二篇文章已经说过,这样的调用实际上还是让线程池里的一个线程来调用,我们可以称之为一种伪异步IO)。这里可以得出一个结论:如果你想用BeginRead,那么初始化FileStream的时候就指定异步,否则就不用直接用Read。