理解异步编程模式

输入输出操作通常会比别的进程慢很多,在同步模式下线程常常会等待到I/O操作完成。当线程不用等待而去执行其他的任务,我们认为这个线程可以执行异步I/O。 异步 编程, 允许 单独的线程 上执行 代码 的某些部分,这被称为异步编程模型(APM)。在NETFramework中,许多类通过提供BeginXXX和EndXXX的方法来支持APM模式。举个例子来说,FileStream类有一个Read方法来从流中读取数据,为了支持APM,它也提供了BeginRead和EndRead方法。这种使用BeginXXX和EndReadXXX方法可以让你来异步执行函数。本人从说明如何执行一个异步的操作开始。

下面给出的代码是使用BeginXXX和EndXXX方法,让您可以异步执行代码的模式的一个例子:

using System;
using System.IO;
using System.Threading;
public sealed class Program {
public static void Main() {
byte[]  buffer = new byte[100];
string filename = String.Concat(Environment.SystemDirectory, "\\ntdll.dll");
FileStream  fs = new FileStream(filename, FileMode.Open, FileAccess.Read,
 FileShare.Read, 1024, FileOptions.Asynchronous);
  IAsyncResult result =  fs.BeginRead(buffer, 0, buffer.Length, null, null);
 int numBytes = fs.EndRead(result);
fs.Close();
Console.WriteLine("Read {0}  Bytes:", numBytes);
Console.WriteLine(BitConverter.ToString(buffer));
  }
}


以下是结果输出:

Read 100  Bytes:
4D-5A-90-00-03-00-00-00-04-00-00-00-FF-FF-00-00-B8-00-00-00-00-00-00-00-40-00-00
-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-0
0-00-00-00-00-00-00-D0-00-00-00-0E-1F-BA-0E-00-B4-09-CD-21-B8-01-4C-CD-21-54-68-
69-73-20-70-72-6F-67-72-61-6D-20-63-61-6E-6E-6F-74-20-62-65

上面代码是使用异步APM模式来从一个文件流中读取100个字节。首先你得构建一个FileStream对象,为了完成异步操作,你必须在参数中指定FileOptions.Asynchronous。从文件流中同步读取数据,你会调用它的Read方法,该方法的原型如下:

public  Int32 Read(Byte[]  array, Int32 offset, Int32  count)

从这个同步读取数据的函数可以看出,这个函数会请求一个缓冲区,它会一直等到直到数据被读入缓冲区并返回。这并不是正确的做法,因为I/O的速度是不可预测的。在等待I/O完成之前,调用线程会一直挂起,它不做任何工作,这是在浪费资源。从文件异步读取数据,你会调用FileStream的BeginRead方法:

IAsyncResult BeginRead(Byte[] array, Int32 offset, Int32 numBytes,
AsyncCallback userCallback, object stateObject)

BeginRead方法和Read方法相似,不同的是BeginRead会返回一个IAsyncResult对象。在我们理解回调函数CallBack之后再来看这个对象。
等待-完成模式允许你异步调用和执行其它的任务,一旦这些任务完成,当其他的任务完成后,你可以尝试结束调用,线程会阻塞直到异步调用完成。下面的代码使用了这种模式:

using System;
using System.IO;
using System.Threading;
public sealed class Program {
public static void Main() {
byte[]  buffer = new byte[100];
string filename = String.Concat(Environment.SystemDirectory, "\\ntdll.dll");
FileStream  fs = new FileStream(filename, FileMode.Open, FileAccess.Read,
 FileShare.Read, 1024, FileOptions.Asynchronous);

// make the asynchronous call

fs.Read(buffer, 0, buffer.Length);
  IAsyncResult result =  fs.BeginRead(buffer, 0, buffer.Length, null, null);
// do some work here while you wait
//Calling EndRead will block until the Async work is complete
 int numBytes = fs.EndRead(result);
// don't forget to close the stream
fs.Close();
Console.WriteLine("Read {0}  Bytes:", numBytes);
Console.WriteLine(BitConverter.ToString(buffer));
  }
}


以下是结果输出:

Read 100  Bytes:
20-72-75-6E-20-69-6E-20-44-4F-53-20-6D-6F-64-65-2E-0D-0D-0A-24-00-00-00-00-00-00
-00-34-CB-99-AF-70-AA-F7-FC-70-AA-F7-FC-70-AA-F7-FC-57-6C-8D-FC-71-AA-F7-FC-57-6
C-8A-FC-31-AA-F7-FC-57-6C-99-FC-5F-AA-F7-FC-57-6C-9A-FC-75-AB-F7-FC-57-6C-8B-FC-
71-AA-F7-FC-57-6C-8F-FC-71-AA-F7-FC-52-69-63-68-70-AA-F7-FC


在调用BeginXxx方法之后立即调用EndXxx方法是愚蠢的,因为调用线程会进入睡眠状态直到操作完成。不过如果你在BeginXXX和EndXXX之间放入一些东西的,事情就会大有玄机,我们可以看到在BeginXXX和EndXXX之间,代码会在读取数据的过程中执行别的任务,如下:

using System;
using System.IO;
using System.Threading;

public static class Program {
   public static void Main() {
      //ReadMultipleFiles(@"C:\Windows\system32\autoexec.NT", @"c:\Point.cs");
      // Open the file indicating asynchronous I/O
      FileStream fs = new FileStream(@"C:\windows\system32\autoexec.NT", FileMode.Open,
         FileAccess.Read, FileShare.Read, 1024,
         FileOptions.Asynchronous);

      Byte[] data = new Byte[100];

      // Initiate an asynchronous read operation against the FileStream
      IAsyncResult ar = fs.BeginRead(data, 0, data.Length, null, null);

      // Executing some other code here would be useful...

      // Suspend this thread until the asynchronous
      // operation completes and get the result
      Int32 bytesRead = fs.EndRead(ar);

      // No other operations to do, close the file
      fs.Close();

      // Now, it is OK to access the byte array and show the result.
      Console.WriteLine("Number of bytes read={0}", bytesRead);
      Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));
   }

   private static void ReadMultipleFiles(params String[] pathnames) {
      AsyncStreamRead[] asrs = new AsyncStreamRead[pathnames.Length];

      for (Int32 n = 0; n < pathnames.Length; n++) {
         // Open the file indicating asynchronous I/O
         Stream stream = new FileStream(pathnames[n], FileMode.Open,
            FileAccess.Read, FileShare.Read, 1024,
            FileOptions.Asynchronous);

         // Initiate an asynchronous read operation against the Stream
         asrs[n] = new AsyncStreamRead(stream, 100);
      }

      // All streams have been opened and all read requests have been
      // queued; they are all executing concurrently!

      // Now, let's get and display the results
      for (Int32 n = 0; n < asrs.Length; n++) {
         Byte[] bytesRead = asrs[n].EndRead();

         // Now, it is OK to access the byte array and show the result.
         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];

         // Initiate an asynchronous read operation against the Stream
         m_ar = stream.BeginRead(m_data, 0, numBytes, null, null);
      }

      public Byte[] EndRead() {
         // Suspend this thread until the asynchronous
         // operation completes and get the result
         Int32 numBytesRead = m_stream.EndRead(m_ar);

         // No other operations to do, close the stream
         m_stream.Close();

         // Resize the array to save space
         Array.Resize(ref m_data, numBytesRead);

         // Return the bytes
         return m_data;
      }
   }
}


以下是结果输出:

Number of bytes read=100
40-65-63-68-6F-20-6F-66-66-0D-0A-0D-0A-52-45-4D-20-41-55-54-4F-45-58-45-43-2E-42
-41-54-20-69-73-20-6E-6F-74-20-75-73-65-64-20-74-6F-20-69-6E-69-74-69-61-6C-69-7
A-65-20-74-68-65-20-4D-53-2D-44-4F-53-20-65-6E-76-69-72-6F-6E-6D-65-6E-74-2E-0D-
0A-52-45-4D-20-41-55-54-4F-45-58-45-43-2E-4E-54-20-69-73-20


所谓轮询模式,是我们通过轮询IAsyncResult来得知异步操作是否已经完成。这有两个例子:简单的和稍微复杂的。注意和记住的事情是,通过调用BeginRead返回的IAsyncResult对象的IsCompleted属性,我们可以继续根据需要做的工作,直到操作完成:

using System;
using System.IO;
using System.Threading;
public sealed class Program {
public static void Main() {
byte[]  buffer = new byte[100];
string filename = String.Concat(Environment.SystemDirectory, "\\ntdll.dll");
FileStream  fs = new FileStream(filename, FileMode.Open, FileAccess.Read,
 FileShare.Read, 1024, FileOptions.Asynchronous);
  IAsyncResult result =  fs.BeginRead(buffer, 0, buffer.Length, null, null);
while (!result.IsCompleted)
{
  // do some work here if the call isn't completed
  // you know, execute a code block or something
Thread.Sleep(100);
}
 int numBytes = fs.EndRead(result);
fs.Close();
Console.WriteLine("Read {0}  Bytes:", numBytes);
Console.WriteLine(BitConverter.ToString(buffer));
  }
}


在回调模式中,我们需要指定一个方法进行回调。假设异步I / O请求,然后你的线程继续做任何它想做的事。当I / O请求完成后,窗口排队的工作项目中的CLR线程池。最后,线程池中的线程将出列的工作项目,并调用一些方法,你知道异步I/ O操作已完成。现在,在这个回调方法,你第一次调用EndXXX方法获得的异步操作的结果,然后继续处理结果的方法。当方法返回时,线程池中的线程可以追溯到入池可以服务其他排队的工作项目(或等待,直到一个显示了)。话虽如此,让我们回顾一下FileStream的BeginRead方法的原型:

IAsyncResult BeginRead(Byte[] array, Int32 offset, 
	Int32 numBytes, AsyncCallback userCallback, Object stateObject)


BeginRead方法需要两个参数,System.AsyncCallback和Object。AsyncCallback是一个委托类型定义如下:

delegate  void AsyncCallback(IAsyncResult ar);


这有个例子,是引用Jeffrey Richter书中“The CLR via C#”:

// (code has been slightly revised)
using System;
using System.IO;
using System.Threading;

public static class Program {
   // The array is static so it can be accessed by Main and ReadIsDone
   private static Byte[] s_data = new Byte[100];

   public static void Main() {
      ReadMultipleFiles(@"C:\Windows\System32\config.NT", @"C:\point.cs");
      APMCallbackUsingAnonymousMethod();
      // Show the ID of the thread executing Main
      Console.WriteLine("Main thread ID={0}",
         Thread.CurrentThread.ManagedThreadId);

      //ReadMultipleFiles(@"C:\Windows\System32\Config.NT", @"c:\Point.cs");
      // Open the file indicating asynchronous I/O
      FileStream fs = new FileStream(@"C:\Windows\System32\config.NT", FileMode.Open,
         FileAccess.Read, FileShare.Read, 1024,
         FileOptions.Asynchronous);

      // Initiate an asynchronous read operation against the FileStream
      // Pass the FileStream (fs) to the callback method (ReadIsDone)
      fs.BeginRead(s_data, 0, s_data.Length, ReadIsDone, fs);

      // Executing some other code here would be useful...

      // For this demo, I'll just suspend the primary thread
      Console.ReadLine();
   }

   private static void ReadIsDone(IAsyncResult ar) {
      // Show the ID of the thread executing ReadIsDone
      Console.WriteLine("ReadIsDone thread ID={0}",
         Thread.CurrentThread.ManagedThreadId);

      // Extract the FileStream (state) out of the IAsyncResult object
      FileStream fs = (FileStream) ar.AsyncState;

      // Get the result
      Int32 bytesRead = fs.EndRead(ar);

      // No other operations to do, close the file
      fs.Close();

      // Now, it is OK to access the byte array and show the result.
      Console.WriteLine("Number of bytes read={0}", bytesRead);
      Console.WriteLine(BitConverter.ToString(s_data, 0, bytesRead));
   }

   private static void APMCallbackUsingAnonymousMethod() {
      // Show the ID of the thread executing Main
      Console.WriteLine("Main thread ID={0}",
         Thread.CurrentThread.ManagedThreadId);

      // Open the file indicating asynchronous I/O
      FileStream fs = new FileStream(@"C:\Windows\System32\config.NT", FileMode.Open,
         FileAccess.Read, FileShare.Read, 1024,
         FileOptions.Asynchronous);

      Byte[] data = new Byte[100];

      // Initiate an asynchronous read operation against the FileStream
      // Pass the FileStream (fs) to the callback method (ReadIsDone)
      fs.BeginRead(data, 0, data.Length,
         delegate(IAsyncResult ar)
         {
            // Show the ID of the thread executing ReadIsDone
            Console.WriteLine("ReadIsDone thread ID={0}",
               Thread.CurrentThread.ManagedThreadId);

            // Get the result
            Int32 bytesRead = fs.EndRead(ar);

            // No other operations to do, close the file
            fs.Close();

            // Now, it is OK to access the byte array and show the result.
            Console.WriteLine("Number of bytes read={0}", bytesRead);
            Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));

         }, null);

      // Executing some other code here would be useful...

      // For this demo, I'll just suspend the primary thread
      Console.ReadLine();
   }

   private static void ReadMultipleFiles(params String[] pathnames) {
      for (Int32 n = 0; n < pathnames.Length; n++) {
         // Open the file indicating asynchronous I/O
         Stream stream = new FileStream(pathnames[n], FileMode.Open,
            FileAccess.Read, FileShare.Read, 1024,
            FileOptions.Asynchronous);

         // Initiate an asynchronous read operation against the Stream
         new AsyncStreamRead(stream, 100,
            delegate(Byte[] data)
            {
               // Process the data.
               Console.WriteLine("Number of bytes read={0}", data.Length);
               Console.WriteLine(BitConverter.ToString(data));
            });
      }

      // All streams have been opened and all read requests have been
      // queued; they are all executing concurrently and they will be
      // processed as they complete!

      // The primary thread could do other stuff here if it wants to...

      // For this demo, I'll just suspend the primary thread
      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;

         // Initiate an asynchronous read operation against the Stream
         stream.BeginRead(m_data, 0, numBytes, ReadIsDone, null);
      }

      // Called when IO operation completes
      private void ReadIsDone(IAsyncResult ar) {
         Int32 numBytesRead = m_stream.EndRead(ar);

         // No other operations to do, close the stream
         m_stream.Close();

         // Resize the array to save space
         Array.Resize(ref m_data, numBytesRead);

         // Call the application's callback method
         m_callback(m_data);
      }
   }
}


以下是结果输出:

Number of bytes read=100
52-45-4D-20-57-69-6E-64-6F-77-73-20-4D-53-2D-44-4F-53-20-53-74-61-72-74-75-70-20
-46-69-6C-65-0D-0A-52-45-4D-0D-0A-52-45-4D-20-43-4F-4E-46-49-47-2E-53-59-53-20-7
6-73-20-43-4F-4E-46-49-47-2E-4E-54-0D-0A-52-45-4D-20-43-4F-4E-46-49-47-2E-53-59-
53-20-69-73-20-6E-6F-74-20-75-73-65-64-20-74-6F-20-69-6E-69
Number of bytes read=100
75-73-69-6E-67-20-53-79-73-74-65-6D-3B-0D-0A-0D-0A-70-75-62-6C-69-63-20-73-74-61
-74-69-63-20-63-6C-61-73-73-20-50-72-6F-67-72-61-6D-20-7B-0D-0A-20-20-20-70-75-6
2-6C-69-63-20-73-74-61-74-69-63-20-76-6F-69-64-20-4D-61-69-6E-28-73-74-72-69-6E-
67-5B-5D-20-61-72-67-73-29-20-7B-0D-0A-20-20-20-20-20-20-56

Main thread ID=1
ReadIsDone thread ID=4
Number of bytes read=100
52-45-4D-20-57-69-6E-64-6F-77-73-20-4D-53-2D-44-4F-53-20-53-74-61-72-74-75-70-20
-46-69-6C-65-0D-0A-52-45-4D-0D-0A-52-45-4D-20-43-4F-4E-46-49-47-2E-53-59-53-20-7
6-73-20-43-4F-4E-46-49-47-2E-4E-54-0D-0A-52-45-4D-20-43-4F-4E-46-49-47-2E-53-59-
53-20-69-73-20-6E-6F-74-20-75-73-65-64-20-74-6F-20-69-6E-69


下面的内容我们将理解如何使用线程池。在.NET中,我们需要从下面四个步骤开始:
1、创建一个不带任何参数且没有任何返回值的函数
2、创建一个ThreadStart委托,指定步骤1中的函数
3、创建一个Thread object,并指定步骤2中的ThreadStart委托
4、调用Thread.Start执行新的线程
下面是例子:

下面是例子:
using System;
using System.Threading;
public class App {
public static void Main() {
ThreadStart task = new ThreadStart(BasicWork);
Thread myThread = new Thread(task);
myThread.Start();
  }
static void BasicWork()
    {
 Console.WriteLine("Thread:  {0}", Thread.CurrentThread.ManagedThreadId);
    }
 }


在现实世界中的线程,我们需要将信息传递到各个线程。请注意上面的例子中使用ThreadStart委托,它不带任何参数。OK。因此,我们需要将数据传递给线程。怎么办?通过使用一个新的委托叫做ParameterizedThreadStart。此委托指定的方法签名用一个Object类型的参数,并且没有返回。下面是一个代码示例将数据传递给一个线程:

using System;
using System.Threading;
public class App {
public static void Main() {
ParameterizedThreadStart task = new ParameterizedThreadStart(WorkWithParameter;
Thread myThread = new Thread(task);
myThread.Start("Whatcha doin?");

Thread newThread = new Thread(task);
newThread.Start("Nuthin much");
}
static void WorkWithParameter(object o)
 {
    string info = (string) o;
 for (int x = 0; x < 10; ++x)
  {
   Console.WriteLine("{0}: {1}", info, Thread.CurrentThread.ManagedThreadId);
   Thread.Sleep(10);
   }
  }
}


以下是结果输出:

Whatcha doin?: 3
Nuthin much: 4
Whatcha doin?: 3
Nuthin much: 4
Whatcha doin?: 3
Nuthin much: 4
Whatcha doin?: 3
Nuthin much: 4
Whatcha doin?: 3
Nuthin much: 4
Whatcha doin?: 3
Nuthin much: 4
Whatcha doin?: 3
Nuthin much: 4
. . . . . . .

在很多情况下,没有必要创建自己的线程,很多专业文档甚至不建议这样做。NET中的线程支持内置的线程池,在许多情况下你可能会认为你必须创建自己的线程。回想一下在前面的例子中使用的静态方法,将数据传递给一个线程:

static void WorkWithParameter(object o)
 {
    string info = (string) o;
 for (int x = 0; x < 10; ++x)
  {
   Console.WriteLine("{0}: {1}", info, Thread.CurrentThread.ManagedThreadId);
   Thread.Sleep(10);
   }
  }


我们可以使用线程池的QueueUserWorkItem方法来替代上述代码完成的功能:

WaitCallback workItem = new WaitCallback(WorkWithParameter);
 If (!ThreadPool.QueueUserWorkItem(workItem, “ThreadPooled”))
 {
  Console.WriteLine(“Could not queue item”);
}


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值