1.概念:
同步方法 调用在程序继续执行之前需要等待同步方法执行完毕返回结果
异步方法 则在被调用之后立即返回以便程序在被调用方法完成其任务的同时执行其它操作。
同步: 比如有三个程序要执行,必须第一个程序被触发,执行结束了吗,才轮到其他程序执行。
异步:所有程序的执行不需要同步,可以多个触发,互相独立的执行相应的指令。
2.代码示例:
2.1 同步方法----winform 本质上时基于 事件,委托的
btnSync 这个按钮可以理解为一个事件,订阅了btnSync_Click方法
1 /// <summary> 2 /// 同步方法 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void btnSync_Click(object sender, EventArgs e) 7 { 8 //Thread 9 Console.WriteLine($"****************btnSync_Click Start {Thread.CurrentThread.ManagedThreadId}***************"); 10 //总共5件事,做完一件再做下一件 11 for (int i = 0; i < 5; i++) 12 { 13 string name = string.Format($"btnSync_Click_{i}"); 14 this.DoSomethingLong(name); 15 } 16 Console.WriteLine($"****************btnSync_Click End {Thread.CurrentThread.ManagedThreadId}***************"); 17 }
1 #region Private Method 2 /// <summary> 3 /// 一个比较耗时耗资源的私有方法 4 /// </summary> 5 /// <param name="name"></param> 6 private void DoSomethingLong(string name) 7 { 8 Console.WriteLine($"****************DoSomethingLong Start {Thread.CurrentThread.ManagedThreadId}***************"); 9 long lResult = 0; 10 for (int i = 0; i < 10000000; i++) 11 { 12 lResult += i; 13 } 14 Thread.Sleep(2000); 15 16 Console.WriteLine($"****************DoSomethingLong End {Thread.CurrentThread.ManagedThreadId}***************"); 17 } 18 #endregion
1 // 2 // btnAsync 3 // 4 this.btnAsync.Location = new System.Drawing.Point(37, 125); 5 this.btnAsync.Name = "btnAsync"; 6 this.btnAsync.Size = new System.Drawing.Size(99, 41); 7 this.btnAsync.TabIndex = 1; 8 this.btnAsync.Text = "异步方法"; 9 this.btnAsync.UseVisualStyleBackColor = true; 10 //订阅,当按钮点击时触发this.btnAsync_Click方法 11 this.btnAsync.Click += new System.EventHandler(this.btnAsync_Click);
2.2 异步方法
事件订阅
1 // 2 // btnAsync 3 // 4 this.btnAsync.Location = new System.Drawing.Point(37, 125); 5 this.btnAsync.Name = "btnAsync"; 6 this.btnAsync.Size = new System.Drawing.Size(99, 41); 7 this.btnAsync.TabIndex = 1; 8 this.btnAsync.Text = "异步方法"; 9 this.btnAsync.UseVisualStyleBackColor = true; 10 this.btnAsync.Click += new System.EventHandler(this.btnAsync_Click);
不用等待,直接调用。(就行做网站的ajax一样,页面加载后用js传递数据,不用等待服务端的响应,直接执行。当数据在后台操作完成后,返回只给回调函数,此时,在执行该函数。)
对于BeginInvoke的异步调用,完成就返回一个IAsyncResult类型对象。
iAsyncResult = act.BeginInvoke("btnAsync_Click_async", callback, "wing");//委托的异步调用
callback为完成后调用的方法---回调函数。
1 /// <summary> 2 /// 异步方法 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void btnAsync_Click(object sender, EventArgs e) 7 { 8 Console.WriteLine($"****************btnAsync_Click Start {Thread.CurrentThread.ManagedThreadId}***************"); 9 10 { 11 Action<string> act = this.DoSomethingLong; 12 //act += this.DoSomethingLong;//多播委托 13 //act.Invoke("btnAsync_Click"); 14 //act("btnAsync_Click"); 15 //() => { } 16 17 IAsyncResult iAsyncResult = null; 18 AsyncCallback callback = t => 19 { 20 Console.WriteLine(t); 21 Console.WriteLine($"string.ReferenceEquals(t, iAsyncResult)={string.ReferenceEquals(t, iAsyncResult)}"); 22 Console.WriteLine($"This is Callback {Thread.CurrentThread.ManagedThreadId}"); 23 }; 24 //callback.Invoke() 25 26 iAsyncResult = act.BeginInvoke("btnAsync_Click_async", callback, "wing");//委托的异步调用 27 //callback.Invoke(iAsyncResult); 28 29 Console.WriteLine("DoSomething Else/....."); 30 Console.WriteLine("DoSomething Else/....."); 31 Console.WriteLine("DoSomething Else/....."); 32 Console.WriteLine("DoSomething Else/....."); 33 34 //while (!iAsyncResult.IsCompleted)//边等待边操作 提示用户 35 //{ 36 // Thread.Sleep(100);//有误差,最多100ms 37 // Console.WriteLine("*******等待等待*********"); 38 //} 39 40 //iAsyncResult.AsyncWaitHandle.WaitOne();//等到异步完成 没有损耗 41 //iAsyncResult.AsyncWaitHandle.WaitOne(-1);//等到异步完成 没有损耗 42 43 //iAsyncResult.AsyncWaitHandle.WaitOne(2000);//最多只等2000ms,否则不等了 44 45 46 act.EndInvoke(iAsyncResult); 47 } 48 49 { 50 Func<string, int> func = t => 51 { 52 Thread.Sleep(2000); 53 return DateTime.Now.Year; 54 }; 55 56 IAsyncResult iAsyncResult = func.BeginInvoke("消逝青春", t => 57 { 58 int iResult = func.EndInvoke(t); 59 60 }, null); 61 62 int iResultOut = func.EndInvoke(iAsyncResult); 63 } 64 65 Console.WriteLine("异步执行完成了才去做的事儿"); 66 Console.WriteLine($"****************btnAsync_Click Start {Thread.CurrentThread.ManagedThreadId}***************"); 67 68 }
3.异步等待:
3.1 法一:
1 while (!iAsyncResult.IsCompleted)//边等待边操作 提示用户 2 { 3 Thread.Sleep(100);//有误差,最多100ms 4 Console.WriteLine("*******等待等待*********"); 5 }
3.2 法二:
1 iAsyncResult.AsyncWaitHandle.WaitOne();//等到异步完成 没有损耗
或
2 iAsyncResult.AsyncWaitHandle.WaitOne(-1);//等到异步完成 没有损耗 或
4 iAsyncResult.AsyncWaitHandle.WaitOne(2000);//最多只等2000ms,否则不等了
3.3 法三:
1 act.EndInvoke(iAsyncResult); //死等,结束了才执行回调函数
注:
Action<string> act = this.DoSomethingLong; //本生没有返回值,是void.
iAsyncResult = act.BeginInvoke("btnAsync_Click_async", callback, "wing");//BeginInvoke才有返回值,它是异步调用的返回值;包含异步调用的结果信息。
若委托有返回值,则EndInvoke也有返回值:
1 Func<string, int> func = t => 2 { 3 Thread.Sleep(2000); 4 return DateTime.Now.Year; 5 }; 6 7 IAsyncResult iAsyncResult = func.BeginInvoke("消逝青春", t => 8 { 9 int iResult = func.EndInvoke(t);//1 其实这里等待与否已经不重要了,因为回调函数表示该异步调用已经完成;这里只是为了获取异步调用的返回值而已 10 11 }, null); 12 13 //int iResultOut = func.EndInvoke(iAsyncResult);//2 写在外面则可以死等 且 可以获得 异步调用的返回值
且 1 与 2 只能选一个调用,不然会报错。
4 加深理解:
4.1 同步卡界面,UI线程被占用;异步多线程不卡界面,UI线程空闲,计算任务交给子线程。
4.2 同步方法慢,因为只有一个线程干活;异步多线程方法快,因为多个线程并发计算,
这里也会消耗更多的资源,不是线程的线性关系,不是 线程越多越好(1资源有限 2线程调度耗资源 3不稳定)
4.3 异步多线程是无序的,不可预测的:启动顺序不确定,消耗时间不确定,结束顺序不确定。
注意:不要试图去控制多线程的执行任务的顺序,若需要,则采用其他的方式,如回调函数。
多线程的特点:不卡主线程、速度快、无序性
5 ThreadStart 其实是个委托,Thread 初始化及使用。
List<Thread> threadList = new List<Thread>();
1 for (int i = 0; i < 5; i++) 2 { 3 string name = string.Format($"btnSync_Click_{i}"); 4 ThreadStart start = () => this.DoSomethingLong(name); 5 Thread thread = new Thread(start);//默认是前台线程,启动后计算完才能退出(当停止主窗体时) 6 thread.IsBackground = true;//设置成后台线程,会立即退出(当停止主窗体时) 7 thread.Start(); 8 threadList.Add(thread); 9 //不推荐使用,已经过时了(仅作为了解)10 //thread.Suspend();//暂停 11 //thread.Resume();//恢复
//----------------------------------------------------------------------------
//如何停止线程? 12 //thread.Abort();//销毁线程,不靠谱,可能停止不了。(不建议用) 13 停止线程 靠的不是外部力量,而是线程自身,外部修改信号量,线程检测信号量 14 thread.Join();//线程等待(可以用) 15 }
1 关于thread.Abort()的解释:来自 stackOverFlow (https://stackoverflow.com/questions/2251964/c-sharp-thread-termination-and-thread-abort) 2 3 Thread.Abort() injects a ThreadAbortException on the thread. The thread may cancel the request by calling Thread.ResetAbort(). 4 5 Also, there are certain code parts, such as finally block that will execute before the exception is handled. (在执行之前可以阻止它执行) 6 7 If for some reason the thread is stuck in such a block the exception will never be raised on the thread.(但是在执行中,卡住了就不得行咯) 8 9 As the caller has very little control over the state of the thread when calling Abort(), 10 11 it is generally not advisable to do so. Pass a message to the thread requesting termination instead.
(不建议这么做,停止线程 靠的不是外部力量,而是线程自身,外部修改信号量,线程检测信号量)
这波操作会让UI进程卡住不动
1 foreach (var thread in threadList) 2 { 3 thread.Join();//表示把thread线程任务join到当前线程,也就是当前线程等着thread任务完成 4 }
类似于它的其他方法:(路不止一条)
1 while (threadList.Count(t => t.ThreadState != System.Threading.ThreadState.Stopped) > 0)//如果还有线程在运行 2 { 3 Thread.Sleep(500); 4 Console.WriteLine("waingting...."); 5 }
那么怎么自己做个回调函数呢?
1 //thread 没有回调 2 /// <summary> 3 /// 回调封装 4 /// </summary> 5 /// <param name="start"></param> 6 /// <param name="callback">回调</param> 7 private void ThreadWithCallback(ThreadStart start, Action callback) 8 { 9 ThreadStart newStart = () => 10 { 11 start.Invoke(); 12 callback.Invoke(); 13 }; 14 Thread thread = new Thread(newStart); 15 thread.Start(); 16 }
那么怎么让回调函数有返回值呢?
1 private Func<T> ThreadWithReturn<T>(Func<T> func) 2 {
//改程序可以看做两部分;第一部分执行 3 T t = default(T); 4 ThreadStart newStart = () => 5 { 6 t = func.Invoke(); 7 8 }; 9 Thread thread = new Thread(newStart); 10 thread.Start();
//第二部分是等待结果,将结果包裹在委托里,什么时候执行,什么时候获得返回值。 11 //返回值是不可能的,毕竟异步多线程;但是可以直接返回一个委托,委托本身又不执行,执行它就可以获得返回值。 12 return new Func<T>(() => 13 { 14 thread.Join();//类似于EndInvoke()函数,等待执行完成,它肯定有返回值了。 15 return t; 16 });
调用:
1 Func<int> oldFunc = () => 2 { 3 Console.WriteLine("oldFunc Sleep Start"); 4 Thread.Sleep(10000); 5 Console.WriteLine("oldFunc Sleep End。。"); 6 return DateTime.Now.Millisecond; 7 }; 8 9 Func<int> newFunc = this.ThreadWithReturn<int>(oldFunc);//begin invoke 10 11 Console.WriteLine("Else1............................"); 12 Thread.Sleep(1000); 13 Console.WriteLine("Else2............................"); 14 Thread.Sleep(1000); 15 Console.WriteLine("Else3............................"); 16 17 Console.WriteLine(newFunc.Invoke());//等待 endivoke 18 19 //int iResult = this.ThreadWithReturn<int>(oldFunc)();