APM全称为Asynchronous Programming Model即异步编程模型。这种方式可构建高性能、可伸缩应用程序,但同时也容易造成界面线程与工作线程的不同步问题。比如错误:线程间操作无效: 从不是创建控件“MyWIndowsForm”的线程访问它。
异步操作允许用非常少的线程执行更多操作,和线程池配合,异步操作可利用机器中所有CPU资源,APM的主要表现形式为:BeginXxx和EndXxx
以下列举了支持APM的FCL类型
a.所有派生自System.IO.Stream并于硬件通讯的类。(包括FileStream和NetworkStream)但是派生自Stream但是不与硬件通讯的类包括BufferedStream、MomeryStream、CryptoStream虽然也提供了BeginRead和BeginWrite,但是这些方法只执行计算限制操作,而不能执行I/O限制操作,因此需要一个线程来执行这些操作。
b.System.Net.Dns提供的BeginGetHostAddress,BeginGetHostByName, BeginGetHostEntry,BeginResolve方法。
c. System.Net.Sockets.Socket提供的BeginAccept, BeginConnect, BeginDisconnect, BeginReceive, BeginReceiveFrom, GeginReceiveMessageFrom, BeginSend, BeginSendFile, BeginSendTo
d. 所有派生自System.Net.WebRequest的类(包括FileWebRequest, FtpWebRequest和HttpWebRequest)都提供了BeginGetRequestStream和BeginGetResponse方法。
e. System.IO.Ports.SerialPort只读BaseStream属性。
System.Data.SlClient.SqlCommand提供了BeginExecuteNonQuery, BeginExecuteReader以及BeginExecuteXmlReader方法。
以SerialPort为例:
新建一个ConsoleApplication程序 并执行以下代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
class MyWIndowsForm : Form
{
private System.IO.Ports.SerialPort serialPort1;
private System.ComponentModel.IContainer components;
public MyWIndowsForm()
{
InitializeComponent();
serialPort1.Open();
Console.WriteLine("MainID:" + System.Threading.Thread.CurrentThread.GetHashCode());
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.serialPort1 = new System.IO.Ports.SerialPort(this.components);
this.SuspendLayout();
//
// serialPort1
//
this.serialPort1.BaudRate = 4800;
this.serialPort1.ReadTimeout = 1000;
this.serialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(this.serialPort1_DataReceived);
//
// MyWIndowsForm
//
this.ClientSize = new System.Drawing.Size(714, 533);
//this.Controls.Add(this.button1);
this.Name = "MyWIndowsForm";
this.ResumeLayout(false);
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
Console.WriteLine("\nEvent ID: " + System.Threading.Thread.CurrentThread.GetHashCode());
byte[] buffer = new byte[1024];
serialPort1.BaseStream.BeginRead(buffer, 0, 1024, SerialPortCallBack, buffer);
}
private void SerialPortCallBack(IAsyncResult result)
{
int count = serialPort1.BaseStream.EndRead(result);
byte[] data = (byte[])result.AsyncState;
Console.WriteLine("\nCallBack ID: " + System.Threading.Thread.CurrentThread.GetHashCode());
for (int i = 0; i < count; i++)
{
Console.Write("{0:X2} ", data[i]);
}
}
}
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.Run(new MyWIndowsForm());
}
}
执行之后通过串口助手发送数据:
即发送16进制:
从上图中完全可以看出:
主线程ID的哈希值(以后简称ID)为9;
事件调用的线程池线程ID第一次为13, 第二次为14;
BeginXxx调用的函数委托线程ID第一次为10,第二次为12
由此完全可以看出:主线程与APM执行函数不在同一个线程(某些情况系也可能在同一个线程比如说主线程为工作者线程且调用委托时恰好主线程已执行完成)。
当然上面的实验结果也引发出一些问题,比如:事件执行函数所在线程不在主线程由此引发的显示问题;两个事件线程不一致所导致的数据同步问题。
这些问题在后面的章节中提到。