c#Mutex的用法總結
msdn中對於Mutex的解釋是:可用於進程間同步的同步基元,顧名思義也就是可用於進程中的同步,並且c#本質論中也提出了Mutex可以用於同步對文件或者其它跨進程資源的訪問,下面就有幾個疑問?
1、Mutex能不能用於同一進程下的不同線程的同步?
答:Mutex可以用於同一進程下不同線程的同步。
1)
private static int count=0; public Form1() { InitializeComponent(); Task task1 = Task.Run(()=> Increase()); Task task2 = Task.Run(()=>Decrease()); Task.WaitAll (new Task[] { task1,task2}); Console.WriteLine(count); } private static void Increase() { for(int i=0;i<int.MaxValue;i++) { count++; } } private static void Decrease() { for(int i=0;i<int.MaxValue;i++) { count--; } }
最終控制台輸出的結果是-456690760,而非我們想象的0,這就是由於多個線程訪問相同的數據造成了競態條件導致,下面我們使用Mutex來實現對count變量的同步,如下:
private static int count = 0; Mutex mutex = new Mutex(false ); public Form1() { InitializeComponent(); Task task1 = Task.Run(() => Increase()); Task task2 = Task.Run(() => Decrease()); Task.WaitAll(new Task[] { task1, task2 }); Console.WriteLine(count); } private void Increase() { mutex.WaitOne();//申請互斥體 for (int i = 0; i < int.MaxValue; i++) { count++; } mutex.ReleaseMutex(); } private void Decrease() { mutex.WaitOne(); for (int i = 0; i < int.MaxValue; i++) { count--; } mutex.ReleaseMutex(); }
2、Mutex在跨進程中怎么使用?
下面舉兩個例子說明Mutex在跨進程中的使用;
1)防止應用程序被打開兩次;
static void Main()
{
bool createNew;
Mutex mt = new Mutex(true, “ApplicationMutex”, out createNew);
if (createNew)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
else
{
MessageBox.Show("程序已經打開!");
Process.GetCurrentProcess().Kill();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
上述代碼中就是利用Mutex構造函數的第三個參數來防止程序被多次打開,Msdn的解釋是:
如果創建了局部互斥體(即,如果 name 為 null 或空字符串)或指定的命名系統互斥體,則createdNew 返回true;如果指定的命名系統互斥體已存在,則為 createdNew 返回false。
2)Mutex在多個進程中訪問文件時實現同步;
多進程不同步對文件的訪問時:
System.IO.IOException:“文件“d:\TestFile\1.txt”正由另一進程使用,因此該進程無法訪問此文件。”
進程一如下:
public partial class Form1 : Form { Mutex mutex = new Mutex(false, "MyMutex"); public Form1() { InitializeComponent(); Thread thread = new Thread(WriteFile); thread.IsBackground = true; thread.Start("d:\\TestFile\\1.txt"); } private void WriteFile(object fileName) { while (true) { string TempfileName = fileName.ToString(); //mutex.WaitOne(); StreamWriter sw = new StreamWriter(TempfileName, false, Encoding.GetEncoding("GB2312")); sw.WriteLine(DateTime.Now.ToString()); sw.Flush(); sw.Close(); //mutex.ReleaseMutex(); Thread.Sleep(10); } } }
進程二如下:
public partial class Form1 : Form { Mutex mutex = new Mutex(false, "MyMutex"); public Form1() { InitializeComponent(); Thread thread = new Thread(ReadFile); thread.IsBackground = true; thread.Start("d:\\TestFile\\1.txt"); } private void ReadFile(object fileName) { while(true) { string tempFileName = fileName.ToString(); string str; //mutex.WaitOne(); StreamReader sr = new StreamReader(tempFileName, Encoding.GetEncoding("GB2312")); str = sr.ReadLine(); Thread.Sleep(10); sr.Close(); //mutex.ReleaseMutex(); this.Invoke(new Action (() => label1.Text = str)); } } }
同時運行這個兩個進程后,就會報出文件被另一進程占用的異常。
多進程同步對文件的訪問:
進程一如下:
public partial class Form1 : Form { Mutex mutex = new Mutex(false, "MyMutex"); public Form1() { InitializeComponent(); Thread thread = new Thread(WriteFile); thread.IsBackground = true; thread.Start("d:\\TestFile\\1.txt"); } private void WriteFile(object fileName) { while (true) { string TempfileName = fileName.ToString(); mutex.WaitOne(); StreamWriter sw = new StreamWriter(TempfileName, false, Encoding.GetEncoding("GB2312")); sw.WriteLine(DateTime.Now.ToString()); sw.Flush(); sw.Close(); mutex.ReleaseMutex(); Thread.Sleep(10); } } }
進程二如下:
public partial class Form1 : Form { Mutex mutex = new Mutex(false, "MyMutex"); public Form1() { InitializeComponent(); Thread thread = new Thread(ReadFile); thread.IsBackground = true; thread.Start("d:\\TestFile\\1.txt"); } private void ReadFile(object fileName) { while (true) { string tempFileName = fileName.ToString(); string str; mutex.WaitOne(); StreamReader sr = new StreamReader(tempFileName, Encoding.GetEncoding("GB2312")); str = sr.ReadLine(); Thread.Sleep(10); sr.Close(); mutex.ReleaseMutex(); this.Invoke(new Action(() => label1.Text = str)); } } }
3、常見錯誤及原因分析
1)System.ApplicationException:“從不同步的代碼塊中調用了對象同步方法
private void Decrease() { for (int i = 0; i < int.MaxValue; i++) { count--; } mutex.ReleaseMutex(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
private static int count = 0; Mutex mutex = new Mutex(true); public Form1() { InitializeComponent(); Task task2 = Task.Run(() => Decrease()); Thread.Sleep(10000); //Task task1 = Task.Run(() => Increase()); //Task.WaitAll(new Task[] { task1, task2 }); Console.WriteLine(count); }
上面的代碼就會報這個錯誤,這是由於Mutex創建對象時,第一個參數為true, Mutex mutex = new Mutex(true);這個true的意思是讓創建這個同步對象的線程擁有互斥體,這里創建互斥體的線程是UI線程,但是我們在Task2中調用了mutex.ReleaseMutex()這個同步方法,而Mutex使用時就有一個規定擁有互斥體的線程才可以去釋放互斥體,所以出現了上面的錯誤,如果將代碼改成下面這兩種情況,則就不會出問題。
情況一、直接在UI線程調用ReleaseMutex
public Form1() { InitializeComponent(); Decrease(); //Task task2 = Task.Run(() => Decrease()); Thread.Sleep(10000); //Task task1 = Task.Run(() => Increase()); //Task.WaitAll(new Task[] { task1, task2 }); Console.WriteLine(count); }
情況二、在task2中創建Mutex的對象
public partial class Form1 : Form { private static int count = 0; public Form1() { InitializeComponent(); Task task2 = Task.Run(() => Decrease()); Thread.Sleep(10000); //Task task1 = Task.Run(() => Increase()); //Task.WaitAll(new Task[] { task1, task2 }); Console.WriteLine(count); } //private void Increase() //{ // mutex.WaitOne(); // for (int i = 0; i < int.MaxValue; i++) // { // count++; // } // mutex.ReleaseMutex(); //} private void Decrease() { Mutex mutex = new Mutex(true); for (int i = 0; i < int.MaxValue; i++) { count--; } mutex.ReleaseMutex(); } }
2)System.Threading.AbandonedMutexException:“由於出現被放棄的 mutex,等待過程結束。”
msdn中指出,如果一個線程終止時擁有互斥體,則認為該互斥量將放棄。 互斥體的狀態將設置為終止狀態,並等待下一步的線程獲取所有權。也就是說線程程序執行完畢,但是線程用的Mutex對象沒有release,如果這時再次申請互斥體,則認為互斥體的代碼已經執行完畢了,申請互斥體的線程可以獲取互斥體,但是這里有一個線程申請互斥體時間的問題,如果擁有互斥體的線程1中的代碼沒有執行完畢,線程2調用waitone申請互斥體,那么當線程1代碼執行完畢,但是沒有release掉mutex的話,則線程2就會報System.Threading.AbandonedMutexException這個錯誤,所以我們在使用中一定要注意釋放Mutex的實例,報錯代碼如下:
public partial class Form1 : Form { private static int count = 0; Mutex mutex; public Form1() { InitializeComponent(); Task task2 = Task.Run(() => Decrease()); Task task1 = Task.Run(() => Increase()); Task.WaitAll(new Task[] { task1, task2 }); Console.WriteLine(count); } private void Increase() { mutex.WaitOne(); for (int i = 0; i < int.MaxValue; i++) { count++; } mutex.ReleaseMutex(); Console.WriteLine("遞增線程結束"); } private void Decrease() { mutex = new Mutex(true); for (int i = 0; i < int.MaxValue; i++) { count--; } //mutex.ReleaseMutex(); Console.WriteLine("遞減線程結束"); } }
如果上述代碼中線程task1在task2執行完畢后再去申請互斥體,則不會報錯,代碼如下:
public partial class Form1 : Form { private static int count = 0; Mutex mutex; public Form1() { InitializeComponent(); Task task2 = Task.Run(() => Decrease()); Thread.Sleep(10000); Task task1 = Task.Run(() => Increase()); Task.WaitAll(new Task[] { task1, task2 }); Console.WriteLine(count); } private void Increase() { mutex.WaitOne(); for (int i = 0; i < int.MaxValue; i++) { count++; } mutex.ReleaseMutex(); Console.WriteLine("遞增線程結束"); } private void Decrease() { mutex = new Mutex(true); for (int i = 0; i < int.MaxValue; i++) { count--; } //mutex.ReleaseMutex(); Console.WriteLine("遞減線程結束"); } }