通常来说,应用程序可以启动任意数目的实例,前提是你的电脑内存足够大,想启动多少都可以。但有时候我们只希望程序同时只有一个实例在运行,应用程序不会重复运行。这就是应用程序的单例模式。
实现(WPF)#
当然,实现的方法不是唯一的。
可以在程序启动的时候查找应用程序的窗口名字,如果查找的结果不为空,就说明已经有一个实例正在运行,但如果你的程序碰巧和其它程序的窗口名字重复就不好说了。
也可以在程序启动时获取应用程序进程的名字,然后再查找系统所有进程,看是否有重复的,原理和上一种类似。
这里要讲的并不是上面两种,而是通过Mutex
来实现,使用比前两种更加简单和有效。
System.Threading.Mutex
官方说明是可用于进程间同步的同步基元。。
使用起来也很简单,重写应用程序的OnStartup
方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // App.xaml.cs
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
Mutex mutex = new Mutex(true, "MutexName", out bool createNew);
if (createNew)
{
base.OnStartup(e);
}
else
{
MessageBox.Show("程序已在运行中。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
Application.Current.Shutdown();
}
}
}
|
注意Mutex
的第2个参数,可以是任意字符串,越复杂越好,避免和其它程序冲突。
如果createNew
为false
,就说明已经有一个实例正在运行了,直接退出当前程序。
注意:必须保证Mutex在程序运行过程中不被垃圾回收,否则就失效了。
上面的写法是可以的,但如果我们使用的是Caliburn.Micro
或者Stylet
等框架,程序启动时都是通过Bootstrapper
的,在Bootstrapper
中的写法会有一些不同。
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
| // 使用 Stylet
public class Bootstrapper : Bootstrapper<ShellViewModel>
{
/// <summary>
/// 必须定义在类内部,一旦被释放就无效了
/// </summary>
private Mutex mutex;
protected override void OnStart()
{
mutex = new Mutex(true, MUTEX_NAME, out bool createNew);
if (!createNew)
{
MessageBox.Show("程序已在运行中。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
// 退出当前应用程序
// 尽量不要使用
// Application.Shutdown();
// 因为在这里使用会触发主窗口的Closing事件
System.Environment.Exit(0);
}
base.OnStart();
}
}
|
需要注意的是要将Mutex
定义在类的内部,如果定义在OnStartup
函数体内,那么在程序运行时它就失效了,就不能使程序以单例模式运行了。
如果你想要写在其它地方,可以将Mutex
定义为static
,这样就可以保证它在应用程序运行过程中不会失效了。
还有就是在这里尽量使用System.Environment.Exit(0)
而不是Application.Current.Shutdown()
,因为我发现这里会触发主窗口的Closing
事件,如果你不希望触发它,那么就使用Environment.Exit(0)
。
关于应用程序单例模式还可以通过WindowsFormsApplicationBase
来实现,但我也没试过。使用Mutex
已经足够满足要求了。
最后再加一项功能,我想要在当前程序检测到已经存在正在运行的实例时,退出并激活已经存在的程序窗口。
这一点可以通过Windows的API来实现,思路就是查找应用程序的主窗口,并将其激活。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| [DllImport("user32.dll", EntryPoint = "FindWindow")]
public static extern IntPtr FindWindow(string classname, string windowname);
[DllImport("user32.dll", EntryPoint = "ShowWindow")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", EntryPoint = "SetForegroundWindow")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
public const int SW_NORMAL = 1;
public const int SW_RESTORE = 9;
public static void FindWindowAndActive(string classname, string windowname)
{
IntPtr hWnd = FindWindow(classname, windowname);
ShowWindow(hWnd, SW_NORMAL);
SetForegroundWindow(hWnd);
}
|
在应用程序退出之前调用FindWindowAndActive
函数即可。
1
2
3
4
| ...
FindWindowAndActive(null, "Main Window"); // 激活已经存在的实例窗口
System.Environment.Exit(0); // 退出当前应用程序
...
|