c#中Lock(鎖)的研究以及跨線程UI的操作
本文只針對C#中,多線程同步所用到的鎖(lock)作為研究對象。由於想更直觀的顯示結果,所以,在做demo的時候,就把多線程通過事件操作UI的代碼也寫了出來,留作備忘和分享吧。
其實多線程的同步,使用同步鎖的方法用了好多次,今天無意中看到MSDN中,建議用:
1 private static readonly object locker1 = new object();
2 private readonly object locker2 = new object();
備注:原文並沒有加readonly,是我後來自己加進去的。
我不僅思考了一下他們的區別。
然後我寫了一段代碼進行測試,測試類代碼如下:
復制代碼
/// <summary>
/// 跨線程操作UI的時候傳遞的參數,本文為了顯示消息,所以簡單的封裝了一個
/// </summary>
public class MyEventArgs : EventArgs
{
public readonly string Message = string.Empty;
public MyEventArgs(string msg)
{
this.Message = msg;
}
}
/// <summary>
/// 測試類,用於測試2種鎖的區別
/// </summary>
public class LockTest
{
//2個鎖
private static readonly object Locker1 = new object();
private readonly object Locker2 = new object();
/// <summary>
/// 跨線程操作UI的委托和事件
/// </summary>
public delegate void MessageEventHandler(object sender, MyEventArgs e);
public event MessageEventHandler MessageEvent;
public void OnMessage(MyEventArgs e)
{
if (this.MessageEvent != null) MessageEvent(this, e);
}
//要鎖的變量,通過它可以看出2種鎖在不同情況下的效果
private int num = 0;
//實例名字
private readonly string Name;
public LockTest(string name)
{
Name = name;
}
//第一種鎖執行的方法
public void AddNum1()
{
lock (Locker1)
{
num = 0;
ShowMessage();
}
}
//第二種鎖執行的方法
public void AddNum2()
{
lock (Locker2)
{
num = 0;
ShowMessage();
}
}
//鎖內的一些操作,並通過事件,把關鍵的消息顯示到主線程中的UI裡
private void ShowMessage()
{
string msg = "";
for (int i = 0; i < 10; i++)
{
num += 1;
msg = string.Format("線程 [{0}],實例[{1}]中num的值是[{2}]", Thread.CurrentThread.Name, this.Name, num);
OnMessage(new MyEventArgs(msg));
Thread.Sleep(100);
}
msg = string.Format("======線程 [{0}]執行完畢======", Thread.CurrentThread.Name);
OnMessage(new MyEventArgs(msg));
}
}
復制代碼
測試用的類寫完了,開始測試:
首先測試單個實例、多線程,2種鎖的區別:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void button1_Click(object sender, EventArgs e)
{
LockTest test = new LockTest("LockTest 1");
test.MessageEvent += new LockTest.MessageEventHandler(MessageCallBack);
listBox1.Items.Clear();
for (int i = 0; i <= 2; i++)
{
Thread a = new Thread(new ThreadStart(test.AddNum1));
a.Name = i.ToString();
a.Start();
}
}
private void button2_Click(object sender, EventArgs e)
{
LockTest test = new LockTest("LockTest 1");
test.MessageEvent += new LockTest.MessageEventHandler(MessageCallBack);
listBox1.Items.Clear();
for (int i = 0; i <= 2; i++)
{
Thread a = new Thread(new ThreadStart(test.AddNum2));
a.Name = i.ToString();
a.Start();
}
}
輸出結果一模一樣:
得出結論:如果對一個實例,多線程訪問的時候,2種鎖是沒有區別的。
下面是測試多個實例的情況(靜態鎖):
復制代碼
private void button3_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
for (int i = 0; i <= 2; i++)
{
LockTest test = new LockTest("LockTest " + i.ToString());
test.MessageEvent += new LockTest.MessageEventHandler(MessageCallBack);
Thread a = new Thread(new ThreadStart(test.AddNum1));
a.Name = i.ToString();
a.Start();
}
}
復制代碼
得到結果:
得出結論,在靜態鎖面前,線程依舊要排隊,雖然不是一個實例,但是鎖是唯一的,線程只認鎖,所以線程並沒有並發!
繼續測試(非靜態的鎖):
復制代碼
private void button4_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
for (int i = 0; i <= 2; i++)
{
LockTest test = new LockTest("LockTest " + i.ToString());
test.MessageEvent += new LockTest.MessageEventHandler(MessageCallBack);
Thread a = new Thread(new ThreadStart(test.AddNum2));
a.Name = i.ToString();
a.Start();
}
}
復制代碼
得到的結果:
得出結論:非靜態鎖的時候,多線程並發了,一起在工作。
其實,測試的結果之前也能猜想出來,只不過,不測試下,心裡總是覺得沒底,呵呵,測試完了,也就徹底釋然了!
窗體中,用於事件回調,顯示到UI裡的代碼在這裡:
復制代碼
delegate void MessageHandler(string msg);
public void MessageCallBack(object sender, MyEventArgs e)
{
MessageHandler handler = new MessageHandler(ShowMessage);
this.Invoke(handler, new object[] { e.Message });
}
public void ShowMessage(string msg)
{
this.listBox1.Items.Add(msg);
}