在WinForms中保持对控件的全局引用以访问GUI线程

时间:2010-02-15 06:04:53

标签: c# winforms refactoring multithreading

(这个问题有一点历史,请耐心等待我)

this问题中,我谈到了集中更新GUI所需的'跨线程'hocus-pocus的可能性,如下所示:

//Utility to avoid boiler-plate InvokeRequired code
//Usage: SafeInvoker.Invoke(myCtrl, () => myCtrl.Enabled = false);
public static void Invoke(Control ctrl, Action cmd)
{
    if (ctrl.InvokeRequired)
        ctrl.BeginInvoke(new MethodInvoker(cmd));
    else
        cmd();
}

上周,在处理事件时总是发生这种情况(在我的代码中),并且部分受到Dustin Campbell事件扩展方法的启发,我对此进行了研究:

//Utility to avoid boiler-plate SafeInvoker.Invoke code
//Usage obj.EventRaised += obj_EventRaised.OnGUIThread(controlreference);
public static EventHandler OnGUIThread(this EventHandler h, Control ctrl)
{
    // lambda expressions are not syntactic sugar, they are syntactic crack!
    return (s, e) => SafeInvoker.Invoke(ctrl, () => h(s, e));
}

让我感到烦恼的是,总是需要控制。据我所知,只有一个GUI线程,所以任何控制都可以在这里完成。

我想知道创建一个'GUIContext'单例并在应用程序启动时向我的主窗体引用它,然后从我的扩展方法访问它,从而不再需要ctrl参数。

这是一个坏主意,如果是,为什么?有没有更好的方法呢?我知道在Rx中有一个Context的概念,但我不知道vanilla WinForms中的任何东西。我可以想象如果我尝试更新尚未处理的控件(但在那种情况下我是screwed anyway)可能会出现问题。

2 个答案:

答案 0 :(得分:1)

我怀疑在很多情况下SynchronizationContext.Current可能会在这里做很多你想做的事情(注意 - 它也可以是null)。但只是SendPost

如果你保留一个全局对象 - 也许就像ISynchronizeInvoke那样输入它 - 意外滥用的可能性更小。

答案 1 :(得分:1)

执行此操作会将您限制为单个主窗体和单个GUI线程。但是对于主要GUI线程的需求与.NET表单(以及底层的Win32 API)一样需要,因此它不太可能发生变化。

您会知道您的应用的单个主要表单是否可能会更改。即使它确实如此,你的单身人士也会更好地跟踪哪个形式是“主要”而不是将其传递给所有后台线程。

总的来说,这看起来像是一个合理的设计。我已经使用全局变量将hwndMain保存在我的非托管应用程序中超过十年,并且从未后悔过。