C#里的Thread.Join与Control.Invoke死锁情况
Thread.Join会导致调用线程挂起, 等待Thread结束后继续执行.
此时若调用线程为主线程(UI线程)同时Thread里面调用了控件的Invoke方法, 则有可能会导致死锁
代码如下:
1 public delegate void InvokeHandler(); 2 static class Extensions 3 { 4 public static void SafeInvoke(this Control control, InvokeHandler handler) 5 { 6 if (control.InvokeRequired) 7 { 8 control.Invoke(handler); 9 } 10 else 11 { 12 handler(); 13 } 14 } 15 public static void BeginSafeInvoke(this Control control, InvokeHandler handler) 16 { 17 if (control.InvokeRequired) 18 { 19 control.BeginInvoke(handler); 20 } 21 else 22 { 23 handler(); 24 } 25 } 26 }
1 class UserInfo 2 { 3 public string Account; 4 public string Password; 5 public ListViewItem LVItem; 6 public void SetListViewItemText(int subindex, string text) 7 { 8 LVItem.ListView.BeginSafeInvoke(() => //起初使用的是SafeInvoke, 会导致死锁 9 { 10 LVItem.SubItems[subindex].Text = text; 11 LVItem.ListView.Refresh(); 12 }); 13 14 } 15 }
1 public void Stop() 2 { 3 _event.Set(); //通知线程要退出 4 _thread.Join(); //A. 等待线程处理完(此处和B导致死锁) 5 _thread = null; 6 _event.Close(); 7 _event = null; 8 } 9 private void Run() 10 { 11 _user.SetListViewItemText(1, "正在登录..."); 12 _loginTime = DateTime.Now; 13 while (_event.WaitOne(5000) == false) 14 { 15 TimeSpan span = DateTime.Now - _loginTime; 16 17 _user.SetListViewItemText(1, string.Format("已登录: {0}", span.ToString())); 18 } 19 _user.SetListViewItemText(1, "已停止"); //B. Invoke方式来更新控件内容(此处和A导致死锁) 20 }
通过代码可以看出, 起初更新相关控件内容时采用的Invoke方式, Invoke会使调用线程挂起并在UI线程上执行相关方法, 直到执行完毕后, 调用线程才会继续.
也就是说Invoke会等待UI操作完成才继续. 而上面代码里由于在UI线程上调用了Stop, 在A处UI线程会处于挂起状态, 这时Run方法所处的线程要更新UI, 调用了Invoke, 这时因为UI线程处于挂起状态, Invoke得不到执行, 所以Run方法所在的线程也挂起了, 永远不会结束. 这样A处就一直挂在那了, 两个都挂着, 就行成死锁了.
解决方案也很简单: 就是把Invoke换成BeginInvoke, BeginInvoke是异步方式执行, 也就是说调用线程执行BeginInvoke不会挂起, 这样就不会导致死锁了.