一、APM概述
APM即异步编程模型的简写(Asynchronous Programming Model),我们平时经常会遇到类似BeginXXX和EndXXX的方法,我们在使用这些方法的时候,其实就是在使用APM来编写程序。
本质:线程池+委托
线程池会在后台执行异步操作,执行完成后,通过回调函数来获取执行结果。
一般使用步骤:
1)构建一个对象,调用BeginXXX异步方法,方法的参数中一般会传入一个委托(回调函数)和一个Object类型变量(用于传递调用BeginXXX异步方法的对象或者封装了该对象的对象)
回调函数的类型:返回值为void,参数为IAsyncResult asyncResult;
2)在回调函数中,利用IAsyncResult的AsyncState属性来获得传入的Object对象,从中获取调用BeginXXX异步方法的对象,接着调用EndXXX,根据EndXXX的返回值判断操作是否完成;
如果没有完成,继续调用BeginXXX异步方法,循环往复,直至操作完成;
二、Demo
以下演示了使用APM模式下载jpg图像。
1 using System; 2 using System.Diagnostics; 3 using System.IO; 4 using System.Net; 5 using System.Windows; 6 using System.Threading; 7 8 namespace Wpf_APM_BeginEnd 9 { 10 // Asynchronous Programming Model 11 //APM .Net 1.0 不支持对异步操作的取消和没有提供对进度报告的功能 12 public class RequestState 13 { 14 private HttpWebRequest request; 15 public HttpWebRequest Request 16 { 17 get 18 { 19 return request; 20 } 21 set 22 { 23 request = value; 24 } 25 } 26 private HttpWebResponse response; 27 public HttpWebResponse Response 28 { 29 get 30 { 31 return response; 32 } 33 set 34 { 35 response = value; 36 } 37 } 38 public Stream ResponseStream; 39 public FileStream Filestream = null; 40 41 public byte[] BufferRead = new byte[1024]; 42 public static int Index = 1; 43 public RequestState(string fileSavePath) 44 { 45 string fileName = "Pic" + (Index++).ToString(); 46 string saveFilePath = fileSavePath + fileName + ".jpg";//以下载jpg图片为例 47 Filestream = new FileStream(saveFilePath, FileMode.CreateNew); 48 } 49 } 50 /// <summary> 51 /// Interaction logic for MainWindow.xaml 52 /// </summary> 53 public partial class MainWindow : Window 54 { 55 private string downLoadUrl = @"https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2298824648,1812234339&fm=200&gp=0.jpg"; 56 public string DownLoadUrl 57 { 58 get { return downLoadUrl; } 59 set { downLoadUrl = value; } 60 } 61 private string fileSavePath = @"D:360Downloads"; 62 public string FileSavePath 63 { 64 get { return fileSavePath; } 65 set { fileSavePath = value; } 66 } 67 public MainWindow() 68 { 69 InitializeComponent(); 70 this.DataContext = this; 71 } 72 73 #region use APM to download file asynchronously 74 75 private void DownloadFileAsync(string url) 76 { 77 try 78 { 79 Debug.WriteLine($"ThreadId: {Thread.CurrentThread.ManagedThreadId}"); 80 // Initialize an HttpWebRequest object 81 HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); 82 // Create an instance of the RequestState and assign HttpWebRequest instance to its request field. 83 RequestState requestState = new RequestState(FileSavePath); 84 requestState.Request = myHttpWebRequest; 85 myHttpWebRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), requestState); 86 } 87 catch (Exception e) 88 { 89 MessageBox.Show(e.Message); 90 } 91 } 92 93 // The following method is called when each asynchronous operation completes. 94 private static void ResponseCallback(IAsyncResult callbackresult) 95 { 96 // Get RequestState object 97 Debug.WriteLine($"ResSubThreadId: {Thread.CurrentThread.ManagedThreadId}"); 98 RequestState myRequestState = (RequestState)callbackresult.AsyncState; 99 100 HttpWebRequest myHttpRequest = myRequestState.Request; 101 102 // End an Asynchronous request to the Internet resource 103 myRequestState.Response = (HttpWebResponse)myHttpRequest.EndGetResponse(callbackresult); 104 105 // Get Response Stream from Server 106 Stream responseStream = myRequestState.Response.GetResponseStream(); 107 myRequestState.ResponseStream = responseStream; 108 109 IAsyncResult asynchronousRead = responseStream.BeginRead(myRequestState.BufferRead, 0, myRequestState.BufferRead.Length, ReadCallBack, myRequestState); 110 111 112 App.Current.Dispatcher.BeginInvoke(new Action(()=> { })); 113 } 114 115 // Write bytes to FileStream 116 private static void ReadCallBack(IAsyncResult asyncResult) 117 { 118 try 119 { 120 Debug.WriteLine($"SubThreadId: {Thread.CurrentThread.ManagedThreadId}"); 121 // Get RequestState object 122 RequestState myRequestState = (RequestState)asyncResult.AsyncState; 123 // Get Response Stream from Server 124 Stream responserStream = myRequestState.ResponseStream; 125 int readSize = responserStream.EndRead(asyncResult); 126 if (readSize > 0) 127 { 128 myRequestState.Filestream.Write(myRequestState.BufferRead, 0, readSize); 129 responserStream.BeginRead(myRequestState.BufferRead, 0, myRequestState.BufferRead.Length, ReadCallBack, myRequestState); 130 } 131 else 132 { 133 myRequestState.Response.Close(); 134 myRequestState.Filestream.Close(); 135 } 136 } 137 catch (Exception e) 138 { 139 //Console.WriteLine("Error Message is:{0}", e.Message); 140 } 141 } 142 #endregion 143 144 private void btnDownLoad_Click(object sender, RoutedEventArgs e) 145 { 146 //string myDownLoafUrl = lbUrl.Content.ToString(); 147 //myDownLoafUrl = "https://www.baidu.com/"; 148 //myDownLoafUrl = @"https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2298824648,1812234339&fm=200&gp=0.jpg"; 149 DownloadFileAsync(DownLoadUrl); 150 } 151 } 152 }
<Window x:Class="Wpf_APM_BeginEnd.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Wpf_APM_BeginEnd" mc:Ignorable="d" Title="MainWindow" Height="300" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="50"/> <RowDefinition Height="50"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="100"/> </Grid.ColumnDefinitions> <Label Content="DownLoadUrl:" FontSize="14" VerticalAlignment="Center" HorizontalAlignment="Right"></Label> <Label Content="FileSavePath:" FontSize="14" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Right"></Label> <TextBox FontSize="20" BorderBrush="Green" BorderThickness="1" Grid.Column="1" Margin="5" Grid.Row="1" Text="{Binding FileSavePath}" Name="tbSavePath"/> <TextBox Text="{Binding DownLoadUrl}" FontSize="20" BorderBrush="Green" BorderThickness="1" Name="lbUrl" Grid.Column="1" Margin="5"/> <Button Content="DownLoad" Grid.Column="2" Grid.Row="2" FontSize="20" Name="btnDownLoad" Click="btnDownLoad_Click" VerticalAlignment="Top"/> </Grid> </Window>
三、自定义类实现APM模式
关键点:利用委托的BeginInvoke和EndInvoke
1 using System; 2 using System.Collections.Generic; 3 using System.Threading; 4 5 namespace APMTest 6 { 7 public delegate int DequeueDataDelegate(int count); 8 public class APMHelper 9 { 10 private DequeueDataDelegate dequeueDataDelegate; 11 public DequeueDataDelegate DequeueDataDelegate 12 { 13 get 14 { 15 return dequeueDataDelegate; 16 } 17 } 18 19 public Queue<int> NumQueue = new Queue<int>(); 20 21 22 public APMHelper(int count) 23 { 24 InitQueue(count); 25 } 26 public void InitQueue(int count) 27 { 28 Random random = new Random(); 29 for(int i = 0; i < count; i++) 30 { 31 int myNum = random.Next(100); 32 NumQueue.Enqueue(myNum); 33 } 34 35 } 36 37 public IAsyncResult BeginDequeueData(int count, AsyncCallback callback, object state) 38 { 39 dequeueDataDelegate = DequeueData; 40 IAsyncResult ar = dequeueDataDelegate.BeginInvoke(count, callback, state); 41 return ar; 42 } 43 public int EndDequeueData(IAsyncResult ar) 44 { 45 return dequeueDataDelegate.EndInvoke(ar); 46 } 47 public int DequeueData(int count) 48 { 49 Console.WriteLine($"Func:DequeueData IsBackgroundThread:{Thread.CurrentThread.IsBackground} IsThreadPool:{Thread.CurrentThread.IsThreadPoolThread} ID:{Thread.CurrentThread.ManagedThreadId}"); 50 int queueCount = NumQueue.Count; 51 int myRet = -1; 52 int myLoop = count; 53 if (queueCount >= count) 54 { 55 myRet = count; 56 } 57 else if(queueCount > 0) 58 { 59 myRet = queueCount; 60 myLoop = queueCount; 61 } 62 else 63 { 64 return -1; 65 } 66 for(int i = 0; i < myLoop; i++) 67 { 68 int ret = NumQueue.Dequeue(); 69 Console.WriteLine($"Dump data:{ret}"); 70 Thread.Sleep(100); 71 } 72 return myRet; 73 } 74 75 public void PrintQueue() 76 { 77 Console.WriteLine($"Func: PrintQueue IsBackgroundThread:{Thread.CurrentThread.IsBackground} IsThreadPool:{Thread.CurrentThread.IsThreadPoolThread} ID:{Thread.CurrentThread.ManagedThreadId}"); 78 Console.WriteLine("Queue:"); 79 foreach (var item in NumQueue) 80 { 81 Console.Write($" {item} "); 82 //Thread.Sleep(500); 83 } 84 Console.WriteLine(); 85 } 86 } 87 class Program 88 { 89 static void Main(string[] args) 90 { 91 Console.WriteLine($"MainThreadId:{Thread.CurrentThread.ManagedThreadId}"); 92 APMHelper apmHelper = new APMHelper(50); 93 apmHelper.PrintQueue(); 94 95 //while (apmHelper.DequeueData(3) > 0)//串行执行 96 //{ 97 //} 98 99 apmHelper.BeginDequeueData(3, DequeueDataCallback, apmHelper);//异步执行 100 Console.WriteLine($"******MainThread do other things...******"); 101 Console.ReadLine(); 102 } 103 static void DequeueDataCallback(IAsyncResult ar) 104 { 105 APMHelper apmHelper = ar.AsyncState as APMHelper; 106 int ret = -1; 107 if(apmHelper != null) 108 { 109 ret = apmHelper.EndDequeueData(ar); 110 } 111 if(ret > 0) 112 { 113 apmHelper.BeginDequeueData(3, DequeueDataCallback, apmHelper); 114 } 115 } 116 } 117 }