异步编程(2)——.NET的异步编程模型
APM支持三种聚集的技巧:等待直到完成,轮询和方法回调,下面追一介绍这几种技巧:
1.等待直至完成
using System;
using System.IO;
using System.Threading;
public static class Program {
public static void Main() {
FileStream fs = new FileStream(@"C:Boot.ini", FileMode.Open,
FileAccess.Read, FileShare.Read, 1024,
FileOptions.Asynchronous);
Byte[] data = new Byte[100];
//异步读取文件,所有的BeginXXX方法都会返回IAsyncResult类型的变量
IAsyncResult ar = fs.BeginRead(data, 0, data.Length, null, null);
//执行一些其他操作
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("Number of bytes read={0}", bytesRead);
Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));
}
}
//输出
5B-62-6F-6F-74-20-6C-6F-61-64-65-72-5D-0D-0A-74-69-6D-65-6F-
75-74-3D-33-30-0D-0A-64-65-66-61-75-6C-74-3D-6D-75-6C-74-69-
28-30-29-64-69-73-6B-28-30-29-72-64-69-73-6B-28-30-29-70-61-
72-74-69-74-69-6F-6E-28-31-29-5C-57-49-4E-44-4F-57-53-0D-0A-
5B-6F-70-65-72-61-74-69-6E-67-20-73-79-73-74-65-6D-73-5D-0D
using System.IO;
using System.Threading;
public static class Program {
public static void Main() {
FileStream fs = new FileStream(@"C:Boot.ini", FileMode.Open,
FileAccess.Read, FileShare.Read, 1024,
FileOptions.Asynchronous);
Byte[] data = new Byte[100];
//异步读取文件,所有的BeginXXX方法都会返回IAsyncResult类型的变量
IAsyncResult ar = fs.BeginRead(data, 0, data.Length, null, null);
//执行一些其他操作
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("Number of bytes read={0}", bytesRead);
Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));
}
}
//输出
5B-62-6F-6F-74-20-6C-6F-61-64-65-72-5D-0D-0A-74-69-6D-65-6F-
75-74-3D-33-30-0D-0A-64-65-66-61-75-6C-74-3D-6D-75-6C-74-69-
28-30-29-64-69-73-6B-28-30-29-72-64-69-73-6B-28-30-29-70-61-
72-74-69-74-69-6F-6E-28-31-29-5C-57-49-4E-44-4F-57-53-0D-0A-
5B-6F-70-65-72-61-74-69-6E-67-20-73-79-73-74-65-6D-73-5D-0D
注意上面的调用,如果执行到ENDXXX,文件还没有读取完成,那么将会挂起调用线程,直至方法返回结果。下面看一下一个改进的版本,这个版本可以从多个文件,套接字,甚至串口中读取字节:
private static void ReadMultipieFiles(params String[] pathnames) {
AsyncStreamRead[] asrs = new AsyncStreamRead[pathnames.Length];
for (Int32 n = 0; n < pathnames.Length; n++) {
Stream stream = new FileStream(pathnames[n], FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
asrs[n] = new AsyncStreamRead(stream, 100);
}
for (Int32 n = 0; n < asrs.Length; n++) {
Byte[] bytesRead = asrs[n].EndRead() ;
Console.WriteLine("Number of bytes read={0}", bytesRead.Length);
Console.WriteLine(BitConverter.ToString(bytesRead));
}
}
private sealed class AsyncStreamRead {
private Stream m_stream;
private IAsyncResult m_ar;
private Byte[] m_data;
public AsyncStreamRead(Stream stream, Int32 numBytes) {
m_stream = stream;
m_data = new Byte[numBytes];
m_ar = stream.BeginRead(m_data, 0, numBytes, null, null);
}
public Byte[] EndRead() {
Int32 numBytesRead = m_stream.EndRead(m_ar);
m_stream.Close();
Array.Resize(ref m_data, numBytesRead);
return m_data;
}
}
AsyncStreamRead[] asrs = new AsyncStreamRead[pathnames.Length];
for (Int32 n = 0; n < pathnames.Length; n++) {
Stream stream = new FileStream(pathnames[n], FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
asrs[n] = new AsyncStreamRead(stream, 100);
}
for (Int32 n = 0; n < asrs.Length; n++) {
Byte[] bytesRead = asrs[n].EndRead() ;
Console.WriteLine("Number of bytes read={0}", bytesRead.Length);
Console.WriteLine(BitConverter.ToString(bytesRead));
}
}
private sealed class AsyncStreamRead {
private Stream m_stream;
private IAsyncResult m_ar;
private Byte[] m_data;
public AsyncStreamRead(Stream stream, Int32 numBytes) {
m_stream = stream;
m_data = new Byte[numBytes];
m_ar = stream.BeginRead(m_data, 0, numBytes, null, null);
}
public Byte[] EndRead() {
Int32 numBytesRead = m_stream.EndRead(m_ar);
m_stream.Close();
Array.Resize(ref m_data, numBytesRead);
return m_data;
}
}
上面的方法还有一个低效的地方就是在第二个循环,我们要按顺序调用ENDXXX方法,但是每个文件的大小不一样,那么他的读取完成时间就会不同,如果前面有一个非常大的文件,而后面的文件比较小,我们在等待大文件读取完毕的时候,他后面的文件已经读取完了,但是我们还要等待这个大文件读取完了才能显示后面的文件内容。
2.轮询
public static void PollingWithIsCompleted() {
FileStream fs = new FileStream(@"C:Boot.ini", FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
Byte[] data = new Byte[100];
IAsyncResult ar = fs.BeginRead(data, 0, data.Length, null, null);
while (!ar.IsCompleted) {
Console.WriteLine("Operation not completed; still waiting.");
Thread.Sleep(10);
}
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("Number of bytes read={0}", bytesRead);
Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));
}
FileStream fs = new FileStream(@"C:Boot.ini", FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
Byte[] data = new Byte[100];
IAsyncResult ar = fs.BeginRead(data, 0, data.Length, null, null);
while (!ar.IsCompleted) {
Console.WriteLine("Operation not completed; still waiting.");
Thread.Sleep(10);
}
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("Number of bytes read={0}", bytesRead);
Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));
}
我们可以看出轮询比较容易实现,但是效率可不高,因为我们要在花费一个线程来询问那个异步请求是否完成了,这样就浪费了一个线程。建议不要使用轮询。
下面我们看一下IAsyncResult的内容,前面已经用到了一个IsCompleted方法,后面可能还会用到它:
public interface IAsyncResult {
Object AsyncState { get; }
WaitHandle AsyncWaitHandle { get; }
Boolean IsCompleted { get; }
Boolean CompletedSynchronously { get; }
}
Object AsyncState { get; }
WaitHandle AsyncWaitHandle { get; }
Boolean IsCompleted { get; }
Boolean CompletedSynchronously { get; }
}
下面看一下轮询的另一种写法,效果和上面的一样:
public static void PollingWithIsCompleted() {
FileStream fs = new FileStream(@"C:Boot.ini", FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
Byte[] data = new Byte[100];
IAsyncResult ar = fs.BeginRead(data, 0, data.Length, null, null);
while (!ar.AsyncWaitHandle.WaitOne(10, false)) {
Console.WriteLine("Operation not completed; still waiting.");
}
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("Number of bytes read={0}", bytesRead);
Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));
}
FileStream fs = new FileStream(@"C:Boot.ini", FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
Byte[] data = new Byte[100];
IAsyncResult ar = fs.BeginRead(data, 0, data.Length, null, null);
while (!ar.AsyncWaitHandle.WaitOne(10, false)) {
Console.WriteLine("Operation not completed; still waiting.");
}
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("Number of bytes read={0}", bytesRead);
Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));
}
3.方法回调,这个是这几种里面效率最高的
下面先看一个委托,因为所有的回调方法必须满足为这个委托的要求:
delegate void AsyncCallback(IAsyncResult ar);
using System;
using System.IO;
using System.Threading;
public static class Program {
private static Byte[] s_data = new Byte[100];
public static void Main() {
Console.WriteLine("Main thread ID={0}",
Thread.CurrentThread.ManagedThreadId);
FileStream fs = new FileStream(@"C:Boot.ini", FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
fs.BeginRead(s_data, 0, s_data.Length, ReadIsDone, fs);
Console.ReadLine() ;
}
private static void ReadIsDone(IAsyncResult ar) {
Console.WriteLine("ReadIsDone thread ID={0}",
Thread.CurrentThread.ManagedThreadId);
FileStream fs = (FileStream) ar.AsyncState;
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("Number of bytes read={0}", bytesRead);
Console.WriteLine(BitConverter.ToString(s_data, 0, bytesRead));
}
}
//输出
Main thread ID=1
ReadIsDone thread ID=4
Number of bytes read=100
5B-62-6F-6F-74-20-6C-6F-61-64-65-72-5D-0D-0A-74-69-6D-65-6F-
75-74-3D-33-30-0D-0A-64-65-66-61-75-6C-74-3D-6D-75-6C-74-69-
28-30-29-64-69-73-6B-28-30-29-72-64-69-73-6B-28-30-29-70-61-
72-74-69-74-69-6F-6E-28-31-29-5C-57-49-4E-44-4F-57-53-0D-0A-
5B-6F-70-65-72-61-74-69-6E-67-20-73-79-73-74-65-6D-73-5D-0D
using System.IO;
using System.Threading;
public static class Program {
private static Byte[] s_data = new Byte[100];
public static void Main() {
Console.WriteLine("Main thread ID={0}",
Thread.CurrentThread.ManagedThreadId);
FileStream fs = new FileStream(@"C:Boot.ini", FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
fs.BeginRead(s_data, 0, s_data.Length, ReadIsDone, fs);
Console.ReadLine() ;
}
private static void ReadIsDone(IAsyncResult ar) {
Console.WriteLine("ReadIsDone thread ID={0}",
Thread.CurrentThread.ManagedThreadId);
FileStream fs = (FileStream) ar.AsyncState;
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("Number of bytes read={0}", bytesRead);
Console.WriteLine(BitConverter.ToString(s_data, 0, bytesRead));
}
}
//输出
Main thread ID=1
ReadIsDone thread ID=4
Number of bytes read=100
5B-62-6F-6F-74-20-6C-6F-61-64-65-72-5D-0D-0A-74-69-6D-65-6F-
75-74-3D-33-30-0D-0A-64-65-66-61-75-6C-74-3D-6D-75-6C-74-69-
28-30-29-64-69-73-6B-28-30-29-72-64-69-73-6B-28-30-29-70-61-
72-74-69-74-69-6F-6E-28-31-29-5C-57-49-4E-44-4F-57-53-0D-0A-
5B-6F-70-65-72-61-74-69-6E-67-20-73-79-73-74-65-6D-73-5D-0D
下面看一下如何利用c#的匿名方法更简单的实现:
public static void Main() {
Console.WriteLine("Main thread ID={0}",
Thread.CurrentThread.ManagedThreadId);
FileStream fs = new FileStream(@"C:Boot.ini", FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
Byte[] data = new Byte[100];
fs.BeginRead(data, 0, data.Length,
delegate(IAsyncResult ar)
{
Console.WriteLine("ReadIsDone thread ID={0}",
Thread.CurrentThread.ManagedThreadId);
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("Number of bytes read={0}", bytesRead);
Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));
}, null);
Console.ReadLine();
}
Console.WriteLine("Main thread ID={0}",
Thread.CurrentThread.ManagedThreadId);
FileStream fs = new FileStream(@"C:Boot.ini", FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
Byte[] data = new Byte[100];
fs.BeginRead(data, 0, data.Length,
delegate(IAsyncResult ar)
{
Console.WriteLine("ReadIsDone thread ID={0}",
Thread.CurrentThread.ManagedThreadId);
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("Number of bytes read={0}", bytesRead);
Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));
}, null);
Console.ReadLine();
}
下面看一下如何解决我们上面文件大小不同读取时间不一样的问题:
private static void ReadMultipleFiles(params String[] pathnames) {
for (Int32 n = 0; n < pathnames.Length; n++) {
Stream stream = new FileStream(pathnames[n], FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
new AsyncStreamRead(stream, 100,
delegate(Byte[] data)
{
Console.WriteLine("Number of bytes read={0}", data.Length);
Console.WriteLine(BitConverter.ToString(data));
});
}
Console.WriteLine("Hit <Enter> to end this program...");
Console.ReadLine() ;
}
private delegate void StreamBytesRead(Byte[] streamData);
private sealed class AsyncStreamRead {
private Stream m_stream;
private Byte[] m_data;
StreamBytesRead m_callback;
public AsyncStreamRead(Stream stream, Int32 numBytes,StreamBytesRead callback) {
m_stream = stream;
m_data = new Byte[numBytes];
m_callback = callback;
stream.BeginRead(m_data, 0, numBytes, ReadIsDone, null);
}
private void ReadIsDone(IAsyncResult ar) {
Int32 numBytesRead = m_stream.EndRead(ar);
m_stream.Close();
Array.Resize(ref m_data, numBytesRead);
m_callback(m_data);
}
}
for (Int32 n = 0; n < pathnames.Length; n++) {
Stream stream = new FileStream(pathnames[n], FileMode.Open,FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
new AsyncStreamRead(stream, 100,
delegate(Byte[] data)
{
Console.WriteLine("Number of bytes read={0}", data.Length);
Console.WriteLine(BitConverter.ToString(data));
});
}
Console.WriteLine("Hit <Enter> to end this program...");
Console.ReadLine() ;
}
private delegate void StreamBytesRead(Byte[] streamData);
private sealed class AsyncStreamRead {
private Stream m_stream;
private Byte[] m_data;
StreamBytesRead m_callback;
public AsyncStreamRead(Stream stream, Int32 numBytes,StreamBytesRead callback) {
m_stream = stream;
m_data = new Byte[numBytes];
m_callback = callback;
stream.BeginRead(m_data, 0, numBytes, ReadIsDone, null);
}
private void ReadIsDone(IAsyncResult ar) {
Int32 numBytesRead = m_stream.EndRead(ar);
m_stream.Close();
Array.Resize(ref m_data, numBytesRead);
m_callback(m_data);
}
}