Recently, the customer reported that the system always crashes from time to time and thought it was a computer environment problem. Later, it was found that this problem also occurred on other computers. After receiving the feedback, I began to find the reason and reproduce it. After a long morning, I finally found the cause: "deadlock ";
This deadlock is a bit strange. Because there is only one synchronization root, how can it be deadlocked. Below is a small piece of code to describe the deadlock:
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: // the location that causes the deadlock
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: // synchronize the root
67: /// </summary>
68: static readonly object syncRoot = new object();
69:
70: public delegate void ValueChangedHandle(object sender,EventArgs e);
71:
72: /// <summary>
73: // event, which is registered by the UI form
74: /// </summary>
75: public event ValueChangedHandle ValueChanged;
76:
77: /// <summary>
78: // This method may be called by the UI thread
79: /// </summary>
80: public string GetValue(int rowIndex, string colName)
81: {
82: // the location that causes the deadlock
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: // this method is called by a thread in the thread pool and is a time-consuming operation.
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: // cause an accident
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: }
Register the ValueChanged event of DataPoolManager on the form. this event is not triggered by the UI thread. Therefore, this. Invoke method is used in event processing. For details, see line 37th of the Code. There is no error, but the key is that this event is triggered in the Lock statement block of the SetValue method in the DataPoolManager object. This means that if the event is not completed, the synchronization root will be "occupied" all the time ". At this time, before the SetValue method does not trigger an event, if the UI thread calls the GetValue method, the UI thread will wait in line 83rd of the code, and after the SetValue method is executed to trigger the event, execute this in line 37 of the Code. A deadlock occurs during Invoke.
Find the cause and solve the problem. There are two solutions:
1. In the SetValue method, the operation that triggers the event is placed outside the Lock block for execution. As follows:
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: // events triggered outside the synchronization Block
12: OnValueChanged(new EventArgs());
13: }
14: return result;
15: }
2. Change this. Invoke in event processing to this. BeginInvoke:
1: void Form1_ValueChanged(object sender, EventArgs e)
2: {
3: //do something...
4: Thread.Sleep(100);
5: // do not wait to return
6: this.BeginInvoke((MethodInvoker)delegate()
7: {
8: //do something
9: });
10: }