如何正确处理系统托盘应用程序中的跨线程问题?

3
所以,我最近接手了一个现有的WinForms应用程序。我们需要将其更改为在系统托盘中运行,但仍然可以在用户需要时弹出窗体。没问题。根据这个问题:使用互斥体确保只运行一个应用程序副本 该应用程序有3个主要组件:自定义应用程序上下文(myContext)处理所有系统托盘内容。myContext创建Gozer类(myGozer)的实例。Gozer执行定时间隔的一系列操作(检查网络状态,如果我们有连接,则执行其他几个操作。它还通过一系列事件告诉其他人正在发生什么。当打开myForm时,myContext会传递myGozer。
当我打开myForm时,我开始遇到各种跨线程问题(任何时候控件以某种方式被使用。例如操纵myListView)。我不清楚最好的处理方法是什么。我对线程知之甚少。我没有一个超级大的脑袋能在30秒内理解线程。我身边也没有人可以吸收他们关于线程的优秀知识。
根据这个问题,我可以简单地检查someControl.InvokeRequired,然后通过委托调用相应的方法。 这有效。但现在我面临的问题是每次需要处理控件时都要添加大量代码,这会引起我脑海中可能校准不良的警报。当我从窗体退出应用程序时,我还发现了一个问题;这会在myContext中引发另一个跨线程异常。我不确定是否仍然适合从myForm退出应用程序,但此时还有哪些其他跨线程疯狂等着我呢?我感觉自己正在为自己制造许多副作用引起的头痛。
我想我真的很担心会在将来创建一堆潜在问题。或者被要求扩展Gozer的功能,然后在myForm中进行工作时创建更多问题。或者发现在运行时打开和关闭myForm会创建一堆额外的跨线程问题。或者该应用程序将导致一切爆炸。
还有其他我应该考虑或遗漏的事情吗?
注意:这是针对一个.NET 2.0应用程序的,所以Jethro的解决方案在这里不起作用。话虽如此,由于我需要编写InvokeRequired逻辑的地方并不是那么多,所以我将只是这样做。我相信在明年我会成功升级到.NET 3.5,并且下面建议的类就是我处理这个问题的方式。因此,我将其标记为答案。
2个回答

4

如果您不想编写扩展方法,升级时有一种替代方法。但需要熟悉lambda语法。以下是一个示例:

myTextBox.Invoke( (Action) (() => myTextBox.Text = "text goes here"));

我通常不会检查是否需要调用并直接进行调用(通常您知道是否从与UI线程不同的线程更新)。

注意:我曾经有一个类似的扩展方法,但厌倦了在所有项目中创建它或在需要添加到所有项目的库中创建它。这是一种“简单”的一行代码的方法。

哦,如果您需要执行多个操作,也可以内联执行,如下所示:

myListView.Invoke( (Action) (() => 
    {
        myListView.Columns.Add("Column 1", -2, HorizontalAlignment.Left);
        myListView.Columns.Add("Column 2", -2, HorizontalAlignment.Left);
        myListView.Columns.Add("Column 3", -2, HorizontalAlignment.Left);
        myListView.Columns.Add("Column 4", -2, HorizontalAlignment.Center);
    }));

我只是假设检查 InvokeRequired 是很重要的。既然我肯定是从另一个线程更新 UI,那么我就跳过它了。这种方法有什么不利之处吗?我确实喜欢那个替代的调用方法。 - peacedog
似乎达成了共识。这里有另一个关于这个话题的SO问题:http://stackoverflow.com/questions/5858557/invokerequired-doubt - Jason Down

3

这里有一个扩展程序可以使用。我不记得在哪里找到它的了。

像这样调用它。

this.InvokeEx(p=> p.txtbox.Text = "Rad");


public static class ControlExtensions
{
    public static TResult InvokeEx<TControl, TResult>(this TControl control,
                                                Func<TControl, TResult> func)
        where TControl : Control
    {
        return control.InvokeRequired
                ? (TResult)control.Invoke(func, control)
                : func(control);
    }

    public static void InvokeEx<TControl>(this TControl control,
                                            Action<TControl> func)
        where TControl : Control
    {
        control.InvokeEx(c => { func(c); return c; });
    }

    public static void InvokeEx<TControl>(this TControl control, Action action)
        where TControl : Control
    {
        control.InvokeEx(c => action());
    }
}

这是针对 .net 2.0 的。这看起来很有趣,让我看看如何处理 Func<>。 - peacedog
哇,我上一条评论写得太糟糕了。我最初写的是“2.0,我不能使用扩展方法”,但后来我在一个4.0项目中尝试了一下,然后走了一条奇怪的路线,对某些事情感到困惑,算了吧。但是,我真的很喜欢这个答案。 - peacedog
扩展方法只是静态方法的语法糖。您仍应该能够在2.0中复制行为,但必须手动调用静态方法并将控件作为第一个参数传递。此外,您可能需要创建自己的Action和Func委托版本(几乎确定它们是3.0或3.5的一部分)。 - Jason Down
@peacedog:实际上,顺便提一下,看起来有些人已经想出了在.Net 2.0中让扩展方法工作的方法:http://www.c-sharpcorner.com/UploadFile/pcurnow/extmethods03242008070853AM/extmethods.aspx 或者在这里:http://kohari.org/2008/04/04/extension-methods-in-net-20/ - Jason Down
@Jason Down - 巧合的是,我在午餐前找到了那个。我不知道;聪明的解决方法。 - peacedog

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接