在.NET中向事件发送信号的最简单方法

时间:2009-09-30 17:58:53

标签: c# .net events

我希望有一种简单的方法可以将事件发送到几个不涉及我编写自定义套接字侦听器的进程。我正在尝试通知几个需要更新已发生配置更改的缓存配置设置的应用程序。我想实现一个“主机范围”单例,但未能找到任何示例。这样的事情甚至可能吗?

5 个答案:

答案 0 :(得分:5)

您可以使用跨进程等待句柄。有关详细信息,请查看此优秀threading tutorial

您可以使用单例类中的等待句柄来创建一种主机范围的单例。以下是您需要了解的singleton pattern in C#

一种可能的实现:单例可以表示配置设置。在检索设置之前,您可以检查存储缓存配置设置的文件的时间戳。可以通过等待句柄保护对文件的读/写访问权限。

如果您反对使用等待句柄,则可以在注册表中设置时间戳。获取和设置注册表值是原子操作,因此它们是自动线程安全的。但请注意,这需要注册表权限,因此除非您确定您的用户具有必需的权限,否则可能不合适。

答案 1 :(得分:4)

命名信号量命名互斥用于进程间同步。

Msdn说:

  

信号量有两种类型:本地信号量和命名系统信号量。如果使用接受名称的构造函数创建Semaphore对象,则它与该名称的操作系统信号量相关联。命名系统信号量在整个操作系统中都可见,可用于同步进程的活动。

Msdn说:

  

命名系统互斥锁在整个操作系统中都是可见的,可用于同步进程的活动。您可以使用接受名称的构造函数创建表示命名系统互斥锁的Mutex对象。操作系统对象可以同时创建,也可以在创建Mutex对象之前存在。

希望这有帮助

答案 2 :(得分:3)

您可以使用命名的EventWaitHandle。但是,在通知所有进程监听后,您还需要一些方法来重置事件。你可以做一些事情,比如设置事件,然后在很短的一段时间后重置它:一秒钟或五秒钟。客户可以知道事件不会连续快速触发。

另一种可能性是使用命名的信号量。但是你需要知道有多少听众,这样你就可以设置信号量的初始值和最大值。

有一些方法可以做你所要求的而不需要构建任何花哨的东西。

答案 3 :(得分:2)

对于紧密耦合的发布者/订阅者模型(发布者明确知道所有订阅者):

  • 您可以使用发布商设置的信号量,并让所有订阅者等待它。但是,如果任何订户死亡,您的计数将被取消。您需要实施某种形式的僵尸检测

  • 您可以使用 COM连接点。这需要管理员注册COM类和类型库。

对于松散耦合的发布者/订阅者模型(发布者不知道有关订阅者的任何事情):

  • 如果配置settigns保存在文件或注册表中,则订阅者可以实现文件或注册表更改侦听器。不幸的是,此解决方案仅限于文件/注册表更改,不会扩展,并且可能会因文件系统/注册表负载而受到延迟。

  • 您可以使用 COM +松散耦合事件(通过 System.EnterpriseServices )。但是,由于LCE的复杂性,这可能对你来说太过分了。

  • 您可以将发布商通过RegisterWindowMessage注册的窗口消息广播到特定类的所有隐藏顶级窗口。所有订阅者都需要创建一个这样的窗口。这需要一些Win32互操作,但可能是实现松散耦合的发布者/订阅者的最轻量级方式。

答案 4 :(得分:0)

我使用以下部分解决了这个问题:

  • 单个共享/命名的内存区域。
  • 单个共享/命名信号量,用于保护对该共享内存区域的访问。
  • 一个名为信号量的每个进程,在调用时会在该进程中本地触发事件。

我的解决方案依靠以下技巧:

  • 共享信号灯可以通过out createNew参数告诉您何时第一个打开它们。
  • 当打开共享内存区域并且以前没有人握住它的句柄时(您是第一个打开它的人),共享内存区域碰巧被初始化为全零。感谢Windows!
  • 您可以向线程池注册等待句柄,为注册提供回调。然后,当发出等待句柄的信号时,线程池将运行您的回调。
  • 具有最后一个句柄的进程结束时,即使进程因崩溃而终止,命名内存区域和命名信号也会自动销毁。

因此,我的策略是:

  • 共享内存区域存储注册的侦听器的数量以及每个侦听器的唯一ID。
  • 每个侦听器在共享存储区中注册自己时,将根据其唯一ID使用众所周知的命名信号量创建每个进程的锁定。
  • 每个侦听器都将每个进程的锁注册到线程池中,以便在弹出该锁时,将在其处理程序方法上获得回调。
  • 当一个进程想要在所有进程中触发事件时,他会在共享内存区域中查找所有已注册的ID,然后使用这些ID打开每个进程的信号量并将其弹出。
  • 如果在打开其他人的共享信号量时发现信号量out createdNew是真实的,那么我们知道该进程崩溃了而没有注销自身,因此我们将忽略它并立即在自己那里注销它。

某些代码:

构造函数,它将打开共享内存区域,共享内存区域的锁,并为每个进程的锁注册此进程:

    ...
    /// <remarks>
    /// ...
    /// The shared memory region that stores the registrations has the following structure:
    ///
    ///      +---- 4 Bytes ----+
    ///      |   NumListeners  |
    ///      +-----------------+
    ///      |   Listener ID   |
    ///      +-----------------+
    ///      |   Listener ID   |
    ///      +-----------------+
    ///      |       ...       |
    ///      +-----------------+
    ///
    /// ...
    /// </remarks>      
    public SharedEvent( string name, int maxListeners = 1024 )
    {
        this.Name = name;
        this.MaximumListeners = maxListeners;

        this.localWaitHandleId = -1;

        try
        {
            this.registrationLock = new Semaphore( 1, 1, RegistrationLockName() );

            this.registrations = MemoryMappedFile.CreateOrOpen(
                RegistrationShmemName(),
                4 + maxListeners * 4,
                MemoryMappedFileAccess.ReadWrite,
                MemoryMappedFileOptions.None,
                null,
                HandleInheritability.None
            );

            RegisterSelf();
        }
        catch
        {
            Dispose();
            throw;
        }
    }

在所有注册人中触发事件的代码:

    public void Trigger(bool suppressSelfHandler = false)
    {
        bool modifiedList = false;

        // The finally block is a ConstrainedExecutionRegion... we definitely don't want to
        // deadlock the whole shared event because we crashed while holding the lock.
        RuntimeHelpers.PrepareConstrainedRegions();
        this.registrationLock.WaitOne();
        try
        {
            List<int> ids = ReadListenerIds();

            for( int i = 0; i < ids.Count; /* conditional increment */ )
            {
                int memberId = ids[i];

                if( suppressSelfHandler && memberId == this.localWaitHandleId )
                {
                    i++;
                    continue;
                }

                Semaphore handle = null;
                try
                {
                    handle = GetListenerWaitHandle( memberId, false );

                    if( handle == null )
                    {
                        // The listener's wait handle is gone. This means that the listener died
                        // without unregistering themselves.

                        ids.RemoveAt( i );
                        modifiedList = true;
                    }
                    else
                    {
                        handle.Release();
                        i++;
                    }
                }
                finally
                {
                    handle?.Dispose();
                }
            }

            if( modifiedList )
            {
                WriteListenerIds( ids );
            }
        }
        finally
        {
            this.registrationLock.Release();
        }
    }

这显示了进程如何在系统中进行自身注册:

    private void RegisterSelf()
    {
        RuntimeHelpers.PrepareConstrainedRegions();
        try
        {
            this.registrationLock.WaitOne();
            List<int> ids = ReadListenerIds();

            if( ids.Count >= this.MaximumListeners )
            {
                throw new InvalidOperationException(
                    "Cannot register self with SharedEvent - no more room in the shared memory's registration list. Increase 'maxSubscribers'."
                );
            }

            this.localWaitHandleId = FindNextListenerId( ids );

            ids.Add( this.localWaitHandleId );
            ids.Sort();

            this.localWaitHandle = GetListenerWaitHandle( this.localWaitHandleId, true );
            this.localWaitHandleReg = ThreadPool.RegisterWaitForSingleObject(
                this.localWaitHandle,
                this.WaitHandleCallback,
                null,
                -1,
                false
            );

            WriteListenerIds( ids );
        }
        finally
        {
            this.registrationLock.Release();
        }
    }

    private void UnregisterSelf()
    {
        RuntimeHelpers.PrepareConstrainedRegions();
        try
        {
            this.registrationLock.WaitOne();

            List<int> ids = ReadListenerIds();

            if( this.localWaitHandleId != -1 && ids.Contains( this.localWaitHandleId ) )
            {
                ids.Remove( this.localWaitHandleId );
            }

            if( this.localWaitHandleReg != null )
            {
                this.localWaitHandleReg.Unregister( this.localWaitHandle );
                this.localWaitHandleReg = null;
            }

            if( this.localWaitHandle != null )
            {
                this.localWaitHandle.Dispose();
                this.localWaitHandle = null;
            }

            WriteListenerIds( ids );
        }
        finally
        {
            this.registrationLock.Release();
        }
    }

读取和写入shmem注册列表的代码:

    private List<int> ReadListenerIds()
    {
        int numMembers;
        int[] memberIds;
        int position = 0;

        using( var view = this.registrations.CreateViewAccessor() )
        {
            numMembers = view.ReadInt32( position );
            position += 4;

            memberIds = new int[numMembers];

            view.ReadArray( position, memberIds, 0, numMembers );
            position += sizeof( int ) * numMembers;
        }

        return new List<int>( memberIds );
    }

    private void WriteListenerIds( List<int> listenerIds )
    {
        int position = 0;

        using( var view = this.registrations.CreateViewAccessor() )
        {
            view.Write( position, (int)listenerIds.Count );
            position += 4;

            foreach( int id in listenerIds )
            {
                view.Write( position, (int)id );
                position += 4;
            }
        }
    }