开发者

Cross-AppDomain Singleton with Remoting - poor startup performance

I have an Addin for MS Excel which needs a singleton to share data amongst modules. Depending on the version of Excel (2003, 2007, 2010), and how Excel was started, it calls my addin from different, unpredictable AppDomains, which prevents the classic singleton approach.

Creating a custom AppDomainManager won't work because Excel has already created the AppDomains before the addin is called.

Linking to mscoree to enumerate domains breaks the addin registration process (and I really don't want that anyway); there doesn't seem to be any other way of enumerating, so that isn't an option either.

The only solution that I've found is to use remoting. Here's my test rig:

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

(please ignore the style, etc., it's just a mockup).

I exercise it with :

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

Here are the results:

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

Once everything is running, access to the singleton takes ~100µS, which is perfect. I can live with the ~400mS the first time each AppDomain is initialised. The problem is at startup, where the remoting invocation takes ~2.5 seconds.

Any suggestions would be very welcome, I've been weeks trying to find a decent solution >;-)


Resolved with IPC instead of TCP

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜