【发布时间】:2011-03-08 13:50:56
【问题描述】:
我有一个用于 MS Excel 的插件,它需要一个单例来在模块之间共享数据。 根据 Excel 的版本(2003、2007、2010)以及 Excel 的启动方式,它会从不同的、不可预测的 AppDomain 调用我的插件,这会阻止经典的单例方法。
创建自定义 AppDomainManager 不起作用,因为 Excel 在调用插件之前已经创建了 AppDomain。
链接到 mscoree 以枚举域会破坏插件注册过程(我真的不希望这样);似乎没有任何其他枚举方式,所以这也不是一种选择。
我发现的唯一解决方案是使用远程处理。这是我的测试台:
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
' Remeber to add reference to System.Runtime.Remoting DLL !
Public Class CrossAppDomainSingleton(Of T As {New})
Inherits MarshalByRefObject
Private Const _portnumber As Integer = 9999 ' adjust to suit taste
Private Shared _instance As T = Nothing
Private Shared _lock As Object = New Object
Private Shared _id As Guid = Nothing
Private Shared _myapp As String = System.Reflection.Assembly.GetExecutingAssembly().GetName.Name
Private Shared _myname As String = GetType(T).ToString
Private Shared _myfullname As String = _myapp & "/" & _myname
Protected Sub New()
_id = Guid.NewGuid
Console.WriteLine("New " & ID & " in " & AppDomain.CurrentDomain.FriendlyName)
End Sub
Public Function GetInstance() As T
If _instance Is Nothing Then
Console.WriteLine("GetInstance in " & AppDomain.CurrentDomain.FriendlyName)
_instance = New T
End If
Return _instance
End Function
Public ReadOnly Property ID As String
Get
Return _id.ToString
End Get
End Property
Public Overrides Function InitializeLifetimeService() As Object
Console.WriteLine("InitializeLifetimeService=infinite")
Return Nothing
End Function
Public Shared Function Instance() As T
Dim currentdomain As AppDomain
Dim uri As String
Dim registered As Boolean = False
Dim remotet As Object
Dim tcpchannel As Tcp.TcpChannel
Dim tcpprops As System.Collections.IDictionary
Dim sw As Stopwatch = New Stopwatch
Try
sw.Start()
SyncLock _lock
Console.WriteLine("Instance in " & AppDomain.CurrentDomain.FriendlyName)
If _instance Is Nothing Then
currentdomain = AppDomain.CurrentDomain
_instance = currentdomain.GetData(_myfullname)
If _instance Is Nothing Then
' Build connection string "tcp://localhost:[_portnumber]/[myapp]/[myname]".
uri = String.Format("tcp://localhost:{0}/{1}/{2}",
_portnumber.ToString,
_myapp,
_myname)
registered = False
Try
remotet = RemotingServices.Connect(GetType(T), uri)
_instance = remotet.GetInstance()
currentdomain.SetData(_myfullname, _instance)
registered = True
Catch ex As Exception
End Try
If registered Then
Console.WriteLine("... instanced with remoting")
Else
Try
Dim ichannel As IChannel = ChannelServices.GetChannel(_myfullname)
ChannelServices.UnregisterChannel(ichannel)
Catch ex As Exception
End Try
tcpprops = New System.Collections.Hashtable
tcpprops("port") = _portnumber.ToString
tcpprops("name") = _myfullname
tcpchannel = New Tcp.TcpChannel(tcpprops, Nothing, Nothing)
ChannelServices.RegisterChannel(tcpchannel, False)
RemotingConfiguration.ApplicationName = _myapp
RemotingConfiguration.RegisterWellKnownServiceType(GetType(T),
_myname,
WellKnownObjectMode.Singleton)
remotet = RemotingServices.Connect(GetType(T), uri)
_instance = remotet.GetInstance()
Console.WriteLine("... singleton instance created")
End If
Else
Console.WriteLine("... instanced from AppDomain")
End If
Else
Console.WriteLine("... instanced statically")
End If
End SyncLock
Catch ex As Exception
Console.WriteLine(String.Format("Error in {0}.Instance: {1}", _myname, ex.Message))
End Try
Console.WriteLine("instance time=" & CLng(1000000 * sw.ElapsedTicks / Stopwatch.Frequency) & " µS")
sw.Stop()
Return _instance
End Function
End Class
(请忽略款式等,只是一个样机)。
我用它来锻炼它:
Module Main
Public Class Common
Inherits CrossAppDomainSingleton(Of Common)
' real application data goes here
End Class
Sub Main()
Dim common As Common
common = common.Instance
Dim defaultdomain As AppDomain = AppDomain.CurrentDomain
defaultdomain.DoCallBack(AddressOf ShowSingleton)
Dim domain1 As AppDomain = AppDomain.CreateDomain("One")
domain1.DoCallBack(AddressOf ShowSingleton)
Dim domain2 As AppDomain = AppDomain.CreateDomain("Two")
domain2.DoCallBack(AddressOf ShowSingleton)
defaultdomain.DoCallBack(AddressOf ShowSingleton)
domain1.DoCallBack(AddressOf ShowSingleton)
domain2.DoCallBack(AddressOf ShowSingleton)
Dim junk As Object = Console.ReadKey
End Sub
Private Sub ShowSingleton()
Console.WriteLine("ShowSingleton in " & AppDomain.CurrentDomain.FriendlyName)
Dim common As Common = common.Instance
Console.WriteLine("guid is " & common.ID)
End Sub
End Module
结果如下:
Instance in CrossAppDomainSingletonDemo.vshost.exe
New 497696b6-71c7-4001-b232-39b379034385 in CrossAppDomainSingletonDemo.vshost.exe
InitializeLifetimeService=infinite
GetInstance in CrossAppDomainSingletonDemo.vshost.exe
New 3a3a7a9c-a4f7-46d4-9b16-35fdbfbdc6c4 in CrossAppDomainSingletonDemo.vshost.exe
InitializeLifetimeService=infinite
... singleton instance created
instance time=2593865 µS ' AAAARRRRGGGGHHHHH !!!!!!!!!!!!!!
ShowSingleton in CrossAppDomainSingletonDemo.vshost.exe
Instance in CrossAppDomainSingletonDemo.vshost.exe
... instanced statically
instance time=38 µS
guid is 3a3a7a9c-a4f7-46d4-9b16-35fdbfbdc6c4
ShowSingleton in One
Instance in One
... instanced with remoting
instance time=440087 µS
guid is 3a3a7a9c-a4f7-46d4-9b16-35fdbfbdc6c4
ShowSingleton in Two
Instance in Two
... instanced with remoting
instance time=438821 µS
guid is 3a3a7a9c-a4f7-46d4-9b16-35fdbfbdc6c4
ShowSingleton in CrossAppDomainSingletonDemo.vshost.exe
Instance in CrossAppDomainSingletonDemo.vshost.exe
... instanced statically
instance time=111 µS
guid is 3a3a7a9c-a4f7-46d4-9b16-35fdbfbdc6c4
ShowSingleton in One
Instance in One
... instanced statically
instance time=106 µS
guid is 3a3a7a9c-a4f7-46d4-9b16-35fdbfbdc6c4
ShowSingleton in Two
Instance in Two
... instanced statically
instance time=105 µS
guid is 3a3a7a9c-a4f7-46d4-9b16-35fdbfbdc6c4
一旦一切都运行起来,访问单例需要大约 100µS,这是完美的。第一次初始化每个 AppDomain 时,我可以忍受 ~400mS。问题出在启动时,远程调用大约需要 2.5 秒。
任何建议都将受到欢迎,我已经数周试图找到一个体面的解决方案 >;-)
【问题讨论】:
标签: vb.net singleton cross-domain add-in remoting