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不会挂起, 这样就不会导致死锁了.