3

主要问题是,从一个线程引发事件可以调用只能在特定线程上下文中调用的委托。在对这个问题进行了一些研究之后,我想,也许可以将某种同步上下文与每个订阅一起传递给一个事件:

SomeClass.SmartSyncEvent += (myDelegate, someReferenceToAThread);

然后引发事件,它以某种方式做到了:

foreach(subscriber)
{
    someReferenceToAThread.Invoke(myDelegate);
}

这是超级伪代码,但也许有人已经做过这样的事情,或者知道任何可以设置这种模式的 .NET 类。谢谢!

4

2 回答 2

7

最好的方法是SomeClass通过ISynchronizeInvoke.

public class SomeClass
{
    public event EventHandler SmartSyncEvent;

    public ISynchronizeInvoke SynchronizingObject { get; set; }

    public void RaiseSmartSyncEvent()
    {
        if (SynchronizingObject != null)
        {
            SynchronizingObject.Invoke(
              (Action)(()=>
              {
                  SmartSyncEvent();
              }), null);
        }
        else
        {
            SmartSyncEvent();
        }
    }
}

这种模式的实现方式类似System.Timers.Timer。它的问题是每个订阅者都将被编组到同一个同步对象上。不过,这似乎不是您想要的。

幸运的是,委托存储了应该通过Target属性调用方法的类实例。我们可以通过在调用委托之前提取它并将其用作同步对象来利用它当然假设它是一个ISynchronizeInvoke自身。我们实际上可以通过使用自定义添加和删除事件访问器来强制执行此操作。

public class SomeClass
{
    private EventHandler _SmartSyncEvent;

    public event EventHandler SmartSyncEvent
    {
        add
        {
            if (!(value.Target is ISynchronizeInvoke))
            {
                throw new ArgumentException();
            }
            _SmartSyncEvent = (EventHandler)Delegate.Combine(_SmartSyncEvent, value);
        }
        remove
        {
            _SmartSyncEvent = (EventHandler)Delegate.Remove(_SmartSyncEvent, value);
        }
    }

    public void RaiseMyEvent()
    {
        foreach (EventHandler handler in _SmartSyncEvent.GetInvocationList())
        {
            var capture = handler;
            var synchronizingObject = (ISynchronizeInvoke)handler.Target;
            synchronizingObject.Invoke(
                (Action)(() =>
                {
                    capture(this, new EventArgs());
                }), null);
        }
    }
}

这要好得多,因为每个订户都可以独立于其他订户进行编组。它的问题是处理程序必须是驻留在ISynchronizeInvoke类中的实例方法。此外,Delegate.Target对于静态方法为 null。这就是我使用自定义add访问器强制执行该约束的原因。


Delegate.Target如果为 null 或无法转换为有用的同步对象,您可以使其同步执行处理程序。这个主题有很多变化。

在 WPF 中,您可以编写代码DispatcherObject而不是ISynchronizeInvoke.

于 2012-05-06T01:57:51.790 回答
1

参阅我在本主题中的回答。我提供了我编写的 3 篇博客文章和一个 YouTube 教学视频的链接,它们都讨论了与 UI 线程安全的同步。每篇博客文章都涵盖了一种完全不同的方法来满足您的要求,因此取决于您使用哪种方法。有些比其他的更容易,但通常都是中间的。

从链接的问题中引用的编辑。

你实际上有很多选择。

(1)背景工作者。如果您真的想要在 WinForms 中进行异步工作的最简单的编程模型,那就是这个。它通常用于执行一些异步任务并报告进度,但如果您不需要,则不必报告进度。

(2)基于事件的异步模式。如果您想制作一个完整的组件来执行一些异步任务,完全控制报告进度和您自己的自定义事件,那么这是一种方法。这也比 BackgroundWorker 更能帮助你理解线程。因为我是一个视觉型的人,所以我创建了一个完整的视频演示,介绍如何使用 WinForms 做到这一点

(3)任务并行库。您可以将 TPL 与 WinForms 一起使用,我在此处写了一篇关于如何做到这一点的非常详细的博客文章

(4)异步和等待。请注意,这需要 .NET 4.5、C# 5.0 和仅包含在 Visual Studio 11 中的 C# 5.0 编译器,目前仅在 BETA 中。但是,我也有一篇关于如何做到这一点的完整博客文章

(5)带线程的ISynchronizedInvoke。这是另一种选择,我也有一个关于.

这完全取决于您选择哪种方法。我的建议是简要地看一下每一个,然后根据你对主题的先进程度选择一种方法,或者哪种方法最能满足你的要求。

于 2012-05-06T01:21:36.130 回答