.Net进阶系列(11)-异步多线程(委托BeginInvoke)(被替换)
一. BeginInvoke最后两个参数的含义
倒数第二个参数:指该线程执行完毕后的回调函数;倒数第一个参数:可以向回调函数中传递参数。
下面以一段代码说明:
1 /// <summary> 2 /// 执行动作:耗时而已 3 /// </summary> 4 private void TestThread(string threadName) 5 { 6 Console.WriteLine("线程开始:线程名为:{2},当前线程的id为:{0},当前时间为:{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName); 7 long sum = 0; 8 for (int i = 1; i < 999999999; i++) 9 { 10 sum += i; 11 } 12 Console.WriteLine("线程结束:线程名为:{2},当前线程的id为::{0},当前时间为:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName); 13 } 14 private void button1_Click_1(object sender, EventArgs e) 15 { 16 Stopwatch watch = new Stopwatch(); 17 watch.Start(); 18 Console.WriteLine("----------------- 二.委托的异步调用测试 --------------------------"); 19 Console.WriteLine("----------------- button1_Click 开始 主线程id为:{0} --------------------------", Thread.CurrentThread.ManagedThreadId); 20 21 #region 2. 异步回调和异步参数 22 { 23 Action<string> myFunc = this.TestThread; 24 //参数说明:前面几个参数都是方法的参数值,倒数第二个为异步调用的回调函数,倒数第一个为传给回调函数的参数 25 for (int i = 0; i < 5; i++) 26 { 27 string name = string.Format("button1_Click{0}", i); 28 myFunc.BeginInvoke(name, t => 29 { 30 Console.WriteLine("我是线程{0}的回调", Thread.CurrentThread.ManagedThreadId); 31 //用 t.AsyncState 来获取回调传进来的参数 32 Console.WriteLine("传进来的参数为:{0}", t.AsyncState); 33 }, "maru"); 34 } 35 } 36 #endregion 37 38 watch.Stop(); 39 Console.WriteLine("----------------- button1_Click 结束 主线程id为:{0} 总耗时:{1}--------------------------", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds); 40 }
结果:
二. 异步调用的三种书写
在上述代码中,我们发现BeginInvoke中,除了我们介绍的最后两个参数外,还有一个参数,传递进去了name,细心的人会发现,正式函数TestThread所需的参数,那么向函数中传递参数到底是在赋值委托时传递,还是委托调用时传递呢?
答案是两者皆可,如果在函数赋值委托时候传递,那么委托调用的时候,BeginInvoke只有最后两个参数。如果在委托调用的时候传递,BeginInvoke除了最后两个参数外,函数本身有几个参数,BeginInvoke前面将多出几个参数位置。
1 /// <summary> 2 /// 两个参数 3 /// </summary> 4 public static void TestThread(string txt1, string txt2) 5 { 6 Console.WriteLine("线程开始:测试参数为:{0}和{1},当前线程的id为:{2}", txt1, txt2, System.Threading.Thread.CurrentThread.ManagedThreadId); 7 long sum = 0; 8 for (int i = 1; i < 999999999; i++) 9 { 10 sum += i; 11 } 12 Console.WriteLine("线程结束:测试参数为:{0}和{1},当前线程的id为:{2}", txt1, txt2, System.Threading.Thread.CurrentThread.ManagedThreadId); 13 } 14 //1. 方式一(使用多重载Action<>委托,函数的参数在BeginInvoke中传入) 15 { 16 Action<string, string> act = TestThread; 17 IAsyncResult iTest = act.BeginInvoke("参数1", "参数2", t => 18 { 19 Console.WriteLine("我是线程执行后的回调"); 20 Console.WriteLine(t.AsyncState); 21 22 }, "我是传递参数的位置"); 23 } 24 25 //2. 方式二(使用Action委托,将参数值直接写在方法中,则无须向BeginInvoke中传入) 26 { 27 Action act2 = () => TestThread("参数1", "参数2"); 28 act2.BeginInvoke(t => 29 { 30 Console.WriteLine("我是线程执行后的回调"); 31 Console.WriteLine(t.AsyncState); 32 33 }, "我是传递参数的位置"); 34 } 35 36 //3. 方式三(下面两个等价,只不过是第一个省略{},在函数体中将方法写入) 37 { 38 Action<string, string> act3 = (a, b) => TestThread(a, b); 39 //Action<string, string> act3 = (a, b) => 40 //{ 41 // TestThread(a, b); 42 //}; 43 IAsyncResult iTest = act3.BeginInvoke("参数1", "参数2", null, null); 44 }
三. 线程等待的三种方式
关于利用委托开启多线程,其线程等待有三种方式:endInvoke、waitone、IsCompleted,推荐使用endInvoke这种方式。
1 private void button1_Click_1(object sender, EventArgs e) 2 { 3 Stopwatch watch = new Stopwatch(); 4 watch.Start(); 5 Console.WriteLine("----------------- 二.委托的异步调用测试 --------------------------"); 6 Console.WriteLine("----------------- button1_Click 开始 主线程id为:{0} --------------------------", Thread.CurrentThread.ManagedThreadId); 7 8 #region 4. 异步等待 9 { 10 IAsyncResult asyncResult = null; 11 Action<string> myFunc = this.TestThread; 12 string name = string.Format("button1_Click{0}", 111); 13 asyncResult = myFunc.BeginInvoke(name, t => 14 { 15 Console.WriteLine("我是线程{0}的回调", Thread.CurrentThread.ManagedThreadId); 16 //用 t.AsyncState 来获取回调传进来的参数 17 Console.WriteLine("传进来的参数为:{0}", t.AsyncState); 18 }, "maru"); 19 20 //等待的方式1:会有时间上的误差 21 //while (!asyncResult.IsCompleted) 22 //{ 23 // Console.WriteLine("正在等待中"); 24 //} 25 26 // 等待的方式二: 27 //asyncResult.AsyncWaitHandle.WaitOne();//一直等待 28 //asyncResult.AsyncWaitHandle.WaitOne(-1);//一直等待 29 //asyncResult.AsyncWaitHandle.WaitOne(1000);//等待1000毫秒,超时就不等待了 30 31 //等待的方式三: 32 myFunc.EndInvoke(asyncResult); 33 34 35 } 36 #endregion 37 38 watch.Stop(); 39 Console.WriteLine("----------------- button1_Click 结束 主线程id为:{0} 总耗时:{1}--------------------------", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds); 40 } 41 #endregion
运行上述代码,我们会发现,主界面又被卡在了,这正好印证了主线程在等待,结果如下:
四. 多个线程的等待
上面介绍的是单个线程的等待,有三种方式,那么如果同时开启了多个线程,主线程需要等待这多个线程,这时需要自己写循环,来进行线程等待。
1 { 2 List<IAsyncResult> list = new List<IAsyncResult>(); 3 for (int i = 0; i < 5; i++) 4 { 5 string name1 = string.Format("ypf1-{0}", i); 6 string name2 = string.Format("ypf2-{0}", i); 7 Action act = () => TestThread(name1, name2); 8 IAsyncResult ir = act.BeginInvoke(null, null); 9 list.Add(ir); 10 } 11 //利用委托进行的异步多线程,采用上述方式二的等待最合理的 12 //缺点:整体上需要写循环,麻烦 13 foreach (var item in list) 14 { 15 item.AsyncWaitHandle.WaitOne(); 16 } 17 }