C#多线程之异步编程
c#中异步编程,主要有两种方法: 1、委托的异步调用; 2、Task的await,async (c# 4.5)
我们来看例子:
1 /// <summary> 2 /// 异步保存网页,url:网页地址,path:要保存的位置 3 /// </summary> 4 private void SavePageAsync(string url, string path) 5 { 6 Func<string, string, bool> fun = SavePageSingleFile; 7 IAsyncResult result = fun.BeginInvoke(url, path, new AsyncCallback(SavePageCompleted), null); 8 }
Func,是系统定义好的委托类型,当然也可以自定义委托了,委托的本质是一个类,它有一个BeginInvoke 异步调用方法。SavePageSingleFile方法是委托要执行的方法:
1 private bool SavePageSingleFile(string url, string path) 2 { 3 bool result = false; 4 try 5 { 6 CDO.Message message = new CDO.MessageClass(); 7 ADODB.Stream stream = null; 8 message.MimeFormatted = true; 9 message.CreateMHTMLBody(url, CDO.CdoMHTMLFlags.cdoSuppressNone, "", ""); 10 stream = message.GetStream(); 11 stream.SaveToFile(path, ADODB.SaveOptionsEnum.adSaveCreateOverWrite); 12 message = null; 13 stream.Close(); 14 15 result = true; 16 } 17 catch (Exception ex) 18 { 19 Logger.Debug("保存文件出错:" + ex.Message); 20 } 21 return result; 22 }
委托任务完成后的回调方法 SavePageCompleted:
1 private void SavePageCompleted(IAsyncResult result) 2 { 3 try 4 { 5 var handler = (Func<string, string, bool>)((AsyncResult)result).AsyncDelegate; 6 var r = handler.EndInvoke(result); 7 8 this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 9 (ThreadStart)delegate() 10 { 11 this.Opacity = 1; 12 13 if (CloseWaittingWindow != null) 14 { 15 CloseWaittingWindow(); 16 } 17 this.Cursor = System.Windows.Input.Cursors.Hand; 18 19 MessageTip tip = new MessageTip(); 20 if (r) 21 { 22 tip.Show("保存网页", "文件保存成功"); 23 } 24 else 25 { 26 tip.Show("保存网页", "文件保存失败"); 27 } 28 } 29 ); 30 } 31 catch (Exception ex) 32 { 33 Logger.Debug("保存页面文件出错:" + ex.Message); 34 this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 35 (ThreadStart)delegate() 36 { 37 new MessageTip().Show("保存网页", "文件保存出错"); 38 this.Opacity = 1; 39 40 if (CloseWaittingWindow != null) 41 { 42 CloseWaittingWindow(); 43 } 44 this.Cursor = System.Windows.Input.Cursors.Hand; 45 } 46 ); 47 } 48 }
最后看下 SavePageAsync 方法的调用:
1 /// <summary> 2 /// 保存当前页面 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void btnSavePage_Click(object sender, RoutedEventArgs e) 7 { 8 if (this.DBVisitViewModel.CanSavePageExcute) 9 { 10 string url = this.WebPageBrower.Url.AbsoluteUri; 11 if (!Tool.CheckUrl(url)) 12 { 13 new MessageTip().Show("保存页面", "当前页面无法保存"); 14 return; 15 } 16 17 SaveFileDialog sfd = new SaveFileDialog(); 18 19 sfd.InitialDirectory = @"D:\"; 20 sfd.Filter = "mht file|*.mht"; 21 22 if (sfd.ShowDialog() == DialogResult.OK) 23 { 24 if (OpenWaittingWindow != null) 25 { 26 this.Cursor = System.Windows.Input.Cursors.Wait; 27 SavePageAsync(url, sfd.FileName); 28 29 this.Opacity = 0.8; 30 OpenWaittingWindow("正在保存网页,请稍等..."); 31 } 32 } 33 } 34 }
在framework 4.0的时候,委托异步调用显得很方便,到了4.5的时候,我们可以用Task实现异步调用。改下上面的例子:
1 private async void SavePageAsync(string url, string path) 2 { 3 var r = await SavePageSingleFile(url, path); 4 5 try 6 { 7 this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 8 (ThreadStart)delegate() 9 { 10 this.Opacity = 1; 11 12 if (CloseWaittingWindow != null) 13 { 14 CloseWaittingWindow(); 15 } 16 this.Cursor = System.Windows.Input.Cursors.Hand; 17 18 MessageTip tip = new MessageTip(); 19 if (r) 20 { 21 tip.Show("保存网页", "文件保存成功"); 22 } 23 else 24 { 25 tip.Show("保存网页", "文件保存失败"); 26 } 27 } 28 ); 29 } 30 catch (Exception ex) 31 { 32 Logger.Debug("保存页面文件出错:" + ex.Message); 33 this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 34 (ThreadStart)delegate() 35 { 36 new MessageTip().Show("保存网页", "文件保存出错"); 37 this.Opacity = 1; 38 39 if (CloseWaittingWindow != null) 40 { 41 CloseWaittingWindow(); 42 } 43 this.Cursor = System.Windows.Input.Cursors.Hand; 44 } 45 ); 46 } 47 48 49 50 } 51 static async Task<bool> SavePageSingleFile(string url, string path) 52 { 53 return await Task.Run(() => 54 { 55 bool result = false; 56 try 57 { 58 CDO.Message message = new CDO.MessageClass(); 59 ADODB.Stream stream = null; 60 message.MimeFormatted = true; 61 message.CreateMHTMLBody(url, CDO.CdoMHTMLFlags.cdoSuppressNone, "", ""); 62 stream = message.GetStream(); 63 stream.SaveToFile(path, ADODB.SaveOptionsEnum.adSaveCreateOverWrite); 64 message = null; 65 stream.Close(); 66 67 result = true; 68 } 69 catch (Exception ex) 70 { 71 Logger.Debug("保存文件出错:" + ex.Message); 72 } 73 return result; 74 }); 75 }
第5行后的try,catch语句块,就是保存网页后的回调,它的执行是在子线程中,因此,在wpf 中要用 Dispatcher(回调中牵扯到对页面的操作)。为了简化这一例子,我这儿给出一个简单的可以执行的例子:
1 static void Main(string[] args) 2 { 3 Add(2,3); 4 Console.WriteLine("主线程:" + Thread.CurrentThread.ManagedThreadId); 5 Console.WriteLine("main Thread go on sth"); 6 Console.Read(); 7 } 8 9 private async static void Add(int x, int y) 10 { 11 Console.WriteLine("主线程:" + Thread.CurrentThread.ManagedThreadId); 12 var t = await TestAsync(x,y); 13 Console.WriteLine("正在等待完成任务,当前线程:"+Thread.CurrentThread.ManagedThreadId); 14 15 Console.WriteLine("运行结果:" + t); 16 } 17 static async Task<int> TestAsync(int x, int y) 18 { 19 return await Task.Run(() => 20 { 21 Thread.Sleep(1000); 22 Console.WriteLine("子线程:" + Thread.CurrentThread.ManagedThreadId); 23 return x + y; 24 }); 25 }
运行结果:
异步编程主要是为了解决耗时的任务占用主线程的问题,比如ajax的异步调用,不会导致页面卡死。好了,今天就谈到这里,该吃中午饭了。