WinForm中的一种死锁场景

最近客户反映系统总是时不时的卡死一次,开始以为是电脑环境问题,后来发现其他电脑上也会出现这种问题。收到反馈后,就开始找原因呀 ,重现呀。折腾了一上午终于找到了原因:“死锁”;

这个死锁发生的有点奇怪。因为只有一个同步根,怎么也会死锁呢。下面写一段小代码 描述一下发生死锁的情形:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel;
   4:  using System.Data;
   5:  using System.Drawing;
   6:  using System.Linq;
   7:  using System.Text;
   8:  using System.Windows.Forms;
   9:  using System.Threading;
  10:   
  11:  namespace DeadLock
  12:  {
  13:      public partial class Form1 : Form
  14:      {
  15:          Button btnGetValue;
  16:          public Form1()
  17:          {
  18:              InitializeComponent();
  19:              this.Controls.Add(btnGetValue);
  20:              btnGetValue.Click += new EventHandler(btnGetValue_Click);
  21:              DataPoolManager.GetInstance().ValueChanged += new DataPoolManager.ValueChangedHandle(Form1_ValueChanged);
  22:          }
  23:   
  24:          void btnGetValue_Click(object sender, EventArgs e)
  25:          {
  26:              int index = 0;
  27:              string colName = string.Empty;
  28:              //do something....
  29:              DataPoolManager.GetInstance().GetValue(index, colName);
  30:          }
  31:   
  32:          void Form1_ValueChanged(object sender, EventArgs e)
  33:          {
  34:              //do something...
  35:              Thread.Sleep(100);
  36:              //造成死锁的位置
  37:              this.Invoke((MethodInvoker)delegate()
  38:              {
  39:                  //do something
  40:              });
  41:          }
  42:      }
  43:   
  44:      public class DataPoolManager
  45:      {
  46:          private DataPoolManager() { }
  47:   
  48:          static DataPoolManager instance;
  49:   
  50:          public static DataPoolManager GetInstance()
  51:          {
  52:              if (instance == null)
  53:              {
  54:                  lock (syncRoot)
  55:                  {
  56:                      if (instance == null)
  57:                      {
  58:                          instance = new DataPoolManager();
  59:                      }
  60:                  }
  61:              }
  62:              return instance;
  63:          }
  64:   
  65:          /// <summary>
  66:          /// 同步根
  67:          /// </summary>
  68:          static readonly object syncRoot = new object();
  69:   
  70:          public delegate void ValueChangedHandle(object sender,EventArgs e);
  71:   
  72:          /// <summary>
  73:          /// 肇事事件 ,该事件被UI窗体注册
  74:          /// </summary>
  75:          public event ValueChangedHandle ValueChanged;
  76:   
  77:          /// <summary>
  78:          /// 这个方法可能被UI线程调用
  79:          /// </summary>
  80:          public string GetValue(int rowIndex, string colName)
  81:          {
  82:              //造成死锁的位置
  83:              lock (syncRoot)
  84:              {
  85:                  //do something...
  86:                  Thread.Sleep(100);
  87:                  return DateTime.Now.ToString("yyyy-MM-dd HH:MM ss");
  88:              }
  89:          }
  90:   
  91:          /// <summary>
  92:          /// 这个方法由线程池中的某个线程调用,是个比较耗时的操作
  93:          /// </summary>
  94:          public bool SetValue(string key, string colName, string newValue)
  95:          {
  96:              bool result = false;
  97:              lock (syncRoot)
  98:              {
  99:                  // do something...
 100:                  Thread.Sleep(100);
 101:                  if (result == true)
 102:                  {
 103:                      //引发肇事事件
 104:                      OnValueChanged(new EventArgs());
 105:                  }
 106:              }
 107:              return result;
 108:          }
 109:   
 110:          private void OnValueChanged(EventArgs eventArgs)
 111:          {
 112:              ValueChangedHandle handle = ValueChanged;
 113:              if (handle != null)
 114:              {
 115:                  handle(this, eventArgs);
 116:              }
 117:          }
 118:   
 119:      }
 120:  }

 

在窗体上注册了 DataPoolManager 的 ValueChanged 事件  ,该事件不是由UI线程引发的,所以在事件处理中 使用了 this.Invoke 方法 ,见代码第37行。这本没什么错误,但是关键在于 这个事件是在 DataPoolManager 对象中 SetValue 方法中的 Lock 语句块中引发的 。这就意味着如果该事件不执行完,同步根将一直被“占用”。这时候 ,在SetValue方法没有引发事件之前 ,如果UI线程调用了 GetValue 方法 ,UI线程将在代码的 第 83 行出等待 ,而 执行 SetValue的方法引发事件后,在 代码 37 行出 执行 this.Invoke 的时候 发生死锁。

找到原因了,问题就好解决了,有两个解决方案:

1,SetValue 方法中,引发事件的操作放到Lock块的外部执行。如下:

   1:          public bool SetValue(string key, string colName, string newValue)
   2:          {
   3:              bool result = false;
   4:              lock (syncRoot)
   5:              {
   6:                  // do something...
   7:                  Thread.Sleep(100);
   8:              }
   9:              if (result == true)
  10:              {
  11:                  //在同步块外部引发事件
  12:                  OnValueChanged(new EventArgs());
  13:              }
  14:              return result;
  15:          }

2,将事件处理中的this.Invoke 改成 this.BeginInvoke:

   1:   void Form1_ValueChanged(object sender, EventArgs e)
   2:          {
   3:              //do something...
   4:              Thread.Sleep(100);
   5:              //不等待返回
   6:              this.BeginInvoke((MethodInvoker)delegate()
   7:              {
   8:                  //do something
   9:              });
  10:          }
posted @ 2013-12-26 11:42  多夢的歲月  阅读(1875)  评论(2编辑  收藏  举报