通常我们见到的单件模式的实现只限于单个执行过程.然而如果要求某个公共对象对多个程序实例,甚至是不同程序都只能有一个实例,该怎么办呢?
首先,我们想到这个对象一定是跨进程,跨应用程序域的.应用程序域是系统内程序执行的安全和资源边界,不同的应用程序域其内存都是独立的,使用普通的方式,是不可能实现的.
要跨越应用程序域,我们似乎只有一个选择,使用Remoting技术,使不同的程序访问同一个远程对象。
在如何实现上,首先想到的是做一个Windows服务,让它一直保持运行状态,将唯一的公共对象实例保存在其中,其他程序通过这个服务获取对象。但是这样无疑增大了实现的复杂度,似乎并不必要,于是放弃。
权衡之后,决定使用一个比较轻量级的方案, 首先,创建一个公共对象的宿主程序,客户程序获取公共对象实例时,首先检查这个宿主程序是否已经启动,如果没有,则创建一个进程,启动宿主程序。如果已经存在,则通过IPC信道(选用IPC信道是考虑到其效率比较高),获取宿主程序公开的远程对象。
实现过程如下:
首先创建宿主程序,大数学家华罗庚说,把问题简化到不能再简单而又不不失重要性,便是成功的第一步.(n年前看到的,无法考证),于是我们的这个公共类也就只有一个属性。注意,一定要继承自MarshalByRefObject,否则,你会发现获取的其实只是一个本地对象,这个可不能简化:)
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
namespace OSSingleton.OSSHost
{
//需要实现单件模式的对象
public class TheOne : MarshalByRefObject
{
private static object syncObj = new object();
private string name = string.Empty;
public string Name
{
get
{
lock (syncObj)
{
return name;
}
}
set
{
lock (syncObj)
{
name = value;
}
}
}
}
class Program
{
static void Main(string[] args)
{
//另启一个线程注册远程信道
Thread thread = new Thread(RegServChannel);
thread.Start();
//阻塞当前线程,使程序不会立即结束
Thread.CurrentThread.Join();
}
private static void RegServChannel()
{
try
{
//注册IPC信道
IpcServerChannel channel = new IpcServerChannel("OSSHostOFOSSingleton", "OSSingleton");
ChannelServices.RegisterChannel(channel, false);
//注册需要公开的对象
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(TheOne), "TheOne", WellKnownObjectMode.Singleton);
}
catch (Exception ex)
{
//若注册过程中出现错误,则显示错误并终止程序。
MessageBox.Show(ex.Message);
Process.GetCurrentProcess().Kill();
}
}
}
}
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
namespace OSSingleton.OSSHost
{
//需要实现单件模式的对象
public class TheOne : MarshalByRefObject
{
private static object syncObj = new object();
private string name = string.Empty;
public string Name
{
get
{
lock (syncObj)
{
return name;
}
}
set
{
lock (syncObj)
{
name = value;
}
}
}
}
class Program
{
static void Main(string[] args)
{
//另启一个线程注册远程信道
Thread thread = new Thread(RegServChannel);
thread.Start();
//阻塞当前线程,使程序不会立即结束
Thread.CurrentThread.Join();
}
private static void RegServChannel()
{
try
{
//注册IPC信道
IpcServerChannel channel = new IpcServerChannel("OSSHostOFOSSingleton", "OSSingleton");
ChannelServices.RegisterChannel(channel, false);
//注册需要公开的对象
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(TheOne), "TheOne", WellKnownObjectMode.Singleton);
}
catch (Exception ex)
{
//若注册过程中出现错误,则显示错误并终止程序。
MessageBox.Show(ex.Message);
Process.GetCurrentProcess().Kill();
}
}
}
}
接下来,是一个DLL,提供了对获取远程对象的一个封装。
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Remoting.Channels;
using System.Threading;
using OSSingleton.OSSHost;
using System.Diagnostics;
namespace OSSingleton
{
public static class OneInOS
{
private static TheOne theOne;
public static TheOne GetInstance()
{
if (theOne == null)
{
//判断是否已经启动了单件宿主进程
if (!Array.Exists<Process>(Process.GetProcesses(),
delegate(Process p)
{
return p.ProcessName == "OSSHost";
}))
{
StartHostExe();
//等待一小段时间,使宿主进程完成启动
Thread.CurrentThread.Join(2000);
}
//获取远程对象
theOne = (TheOne)Activator.GetObject(typeof(TheOne), @"ipc://OSSingleton/TheOne");
}
return theOne;
}
/// <summary>
/// 启动单件宿主进程
/// </summary>
private static void StartHostExe()
{
Process p = new Process();
p.StartInfo.FileName = @"..\..\..\OSSHost\bin\Debug\OSSHost.exe";
p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;
p.Start();
}
}
}
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Remoting.Channels;
using System.Threading;
using OSSingleton.OSSHost;
using System.Diagnostics;
namespace OSSingleton
{
public static class OneInOS
{
private static TheOne theOne;
public static TheOne GetInstance()
{
if (theOne == null)
{
//判断是否已经启动了单件宿主进程
if (!Array.Exists<Process>(Process.GetProcesses(),
delegate(Process p)
{
return p.ProcessName == "OSSHost";
}))
{
StartHostExe();
//等待一小段时间,使宿主进程完成启动
Thread.CurrentThread.Join(2000);
}
//获取远程对象
theOne = (TheOne)Activator.GetObject(typeof(TheOne), @"ipc://OSSingleton/TheOne");
}
return theOne;
}
/// <summary>
/// 启动单件宿主进程
/// </summary>
private static void StartHostExe()
{
Process p = new Process();
p.StartInfo.FileName = @"..\..\..\OSSHost\bin\Debug\OSSHost.exe";
p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;
p.Start();
}
}
}
最后是一个测试程序,功能很简单:获取对象,使其属性Name末尾加上一个星号,然后打印。这样我们每次启动程序都能看到输出多了一个星号,也就证明了,我们的实例确实只有一个。
using System;
using System.Collections.Generic;
using System.Text;
using OSSingleton;
using OSSingleton.OSSHost;
namespace OSSingletonTest
{
class Program
{
static void Main(string[] args)
{
TheOne theOne = OneInOS.GetInstance();
theOne.Name = theOne.Name + "*";
Console.WriteLine(theOne.Name);
}
}
}
在这里下载全部源码using System.Collections.Generic;
using System.Text;
using OSSingleton;
using OSSingleton.OSSHost;
namespace OSSingletonTest
{
class Program
{
static void Main(string[] args)
{
TheOne theOne = OneInOS.GetInstance();
theOne.Name = theOne.Name + "*";
Console.WriteLine(theOne.Name);
}
}
}
现在,我们使用的IPC信道(IPC信道是.NET 2.0新增的),虽然提高了系统性能,但是也被限制在了一个OS内,如果使用TCP/HTTP信道,并固定一台计算机专门运行宿主程序。那么就可以在任意范围内实现单件模式。