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("递减线程结束"); } }