只允许运行一个实例

有时你会想只运行一个程序首次运行时的实例,有些时候则可能只需要运行最新的那个实例。以下是这两种实现这两种方法的例子。
你如何才能确保只有一个 .NET 的程序运行在你的机器上呢?多任务操作系统在很多方面都很有用,但是有时你只需要在某一时间内运行一个实例,例如,如果程序需要使用很多的资源或者需要排它地存取某一个资源。在这种情况下,你可能想确认当另一个实例启动时,它会检查是否有另一个实例正在运行。如果是,则将自身关闭。本文中,我会告诉大家如何去实现它,还有在相关的情况下如何只允许最新的程序实例运行。请在以下地址下载程序源码 ftp://ftp.wdj.com/pub/webzip/newsletters/20030815dnn.zip

最简单的方法是使用 mutex 对像。例如,在以下的代码中, Form 的构造函数中创建了一个 mutex 对象,然后 试着去存取它。如果 WaitOne() 返回 true ,那么当前线程就已经拥有了 mutex ,如果返回 false mutex 已经被另一个线程所拥有。
using System;
using System.Windows.Forms;
using System.Threading;
 
class App : Form
{
     Mutex mutex;
     App()
     {
         Text = "Single Instance!";
         mutex = new Mutex(false, "SINGLE_INSTANCE_MUTEX");
         if (!mutex.WaitOne(0, false))
         {
              mutex.Close();
              mutex = null;
         }
     }
     protected override void Dispose(bool disposing)
     {
         if (disposing)
              mutex.ReleaseMutex();
         base.Dispose(disposing);
     }
     static void Main()
     {
         App app = new App();
         if (app.mutex != null) Application.Run(app);
         else MessageBox.Show("Instance already running");
     }
}
在以上代码中,方法 Dispose() 并不严格需要,这是因为当程序结束时,垃圾回收器将会 dispose 并且 release mutex 对象。但是我仍然添加了它,这是因为 Form 需要一段很长的时间来释放( dispose ),并且另一个窗体( Form )的实例将会启动。

这种方法只允许第一个进程实例运行。但如果你只想运行最新的那个实例时怎么办呢?就是说如果我启动了一个新的进程实例,而且之前已经有一个实例正在运行中,那么我们需要停止之前的那个线程。例如屏保的显示属性对话框。这个对话框显示一个小的预览窗口,当用用户点击预览按钮时,另一个屏保程序实例( instance) 就会启动并全屏显示。当全屏实例停止时(例如你移动了鼠标),另一个屏保实例就会开始在小的预览窗口中开始运行。很明显,当全屏实例开始,这个预览的屏保窗口进程就要结束。
 
一种实现的方法是为每一个实例去存取一个命名的事件核心对象( named event kernel object ),如果事件对象没有被触发( nonsignaled ),那么此实例就继续运行;如果事件被触发则程序实例会结束。应用程序可以周期性地测试事件去看是否它已经被触发。当一个新的实例启动,它就会设置事件(去关闭其它任何实例)然后重设事件以使它可以继续运行。这种方案除了一个小问题外运行良好: .NET   Framework 不会允许你命名一个核心事件( kernel event) 。以下是一个实现的类
     public class NamedEventHelper
     {
         [DllImport("kernel32")]
         static extern uint CreateEvent(
              uint sec, bool manualReset, bool initialState, string name);
         static IntPtr CreateEvent(bool manualReset, bool initialState, string name)
         {
              return new IntPtr(CreateEvent(0, manualReset, initialState, name));
         }
         [DllImport("kernel32")]
         static extern bool CloseHandle(IntPtr handle);
         public static ManualResetEvent CreateNamedEvent(
              bool initialState, string name)
         {
              ManualResetEvent mre = new ManualResetEvent(false);
              CloseHandle(mre.Handle);
              mre.Handle = CreateEvent(true, initialState, name);
              return mre;
         }
     }
 
以上的代码中,静态( static )方法 CreateNamedEvent() 创建了一个 ManualResetEvent 对象,并释放了当前的( underlying Win32 句柄( handle )。然后再创建一个命名事件( named event )并且使用此新事件的句柄初始化了 ManualResetEvent 对象。此事件对象就能被用于两个应用程序间的通信。
 
这种方案的弱点是一个应用程序一定要看它是否已经结束。一种解决方法是运行一个后台线程去管理事件
 
     ManualResetEvent mre;
     mre = NamedEventHelper.CreateNamedEvent(false, "LAST_INSTANCE_ONLY");
     // Stop the other instances
     mre.Set();
     // Reset the event so that we can run
     mre.Reset();
     // Create a monitor thread
     Thread t = new Thread(new ThreadStart(Monitor));
     // Make sure that this thread cannot keep the app alive
     t.IsBackground = true;
     t.Start();
 
     //The Monitor() method looks like this:
 
     void Monitor()
     {
         mre.WaitOne();
         Application.Exit();
     }
 
如果程序有一个窗体,那么另一种方法就是告诉其它窗口去关闭这个新的程序实例。如果这是一个W in32 应用程序,那么此程序可以简单地通过调用 FindWindowsEx() ,并传入一个特定的窗口类参数,然后发送 WM_CLOSE 消息。但是你不能对 Windows Form 这样做 ,因为大多数窗体( Forms) 都有相同的类名,正如我在上一封 newsletter 是所说的。在下一个 newsletter 中,我会解释另一种方法去解决这类问题。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值