最短的编写线程安全访问Windows窗体控件方法的方式是什么?

27
在本文中:
作者使用以下方法来对Windows Forms控件进行线程安全调用:

http://msdn.microsoft.com/en-us/library/ms171728(VS.80).aspx

作者使用以下方法对 Windows Forms 控件进行线程安全的调用:

private void SetText(string text)
{
    // InvokeRequired required compares the thread ID of the
    // calling thread to the thread ID of the creating thread.
    // If these threads are different, it returns true.
    if (this.textBox1.InvokeRequired)
    {    
        SetTextCallback d = new SetTextCallback(SetText);
        this.Invoke(d, new object[] { text });
    }
    else
    {
        this.textBox1.Text = text;
    }
}

有没有更简短的方法来完成同样的事情?

5个回答

37

C# 3.0及其之后版本:

扩展方法通常是最好的选择,因为您总是需要在实现ISynchronizeInvoke接口的对象上执行操作,这是一个很好的设计选择。

您还可以利用匿名方法(闭包)来处理无法确定扩展方法所需参数的情况;闭包将捕获所有必要的状态。

// Extension method.
static void SynchronizedInvoke(this ISynchronizeInvoke sync, Action action)
{
    // If the invoke is not required, then invoke here and get out.
    if (!sync.InvokeRequired)
    {
        // Execute action.
        action();

        // Get out.
        return;
    }

    // Marshal to the required context.
    sync.Invoke(action, new object[] { });
}

你可以这样调用它:

private void SetText(string text)
{
    textBox1.SynchronizedInvoke(() => textBox1.Text = text);
}

这里,闭包围绕着 text 参数,它的状态被捕获并作为传递给扩展方法的 Action 委托的一部分。

C# 3.0 之前:

虽然没有 lambda 表达式,但你仍然可以将代码进行泛化。它几乎相同,但不是一个扩展方法:

static void SynchronizedInvoke(ISynchronizeInvoke sync, Action action)
{
    // If the invoke is not required, then invoke here and get out.
    if (!sync.InvokeRequired)
    {
        // Execute action.
        action();

        // Get out.
        return;
    }

    // Marshal to the required context.
    sync.Invoke(action, new object[] { });
}

然后你可以使用匿名方法语法调用它:

private void SetText(string text)
{
    SynchronizedInvoke(textBox1, delegate() { textBox1.Text = text; });
}

1
是的,非常好用,你需要注意的唯一一件事是需要包含:using System.ComponentModel; 和 using System; - Justin Tanner
1
我知道这已经很老了,但它仍然出现在搜索此解决方案的前几个结果中...问题在于您假设父类是静态的。您无法将静态扩展添加到常规类中。大多数包含要访问的UI的表单/WPF都是具有实例的常规类。因此,在这些实例中,您仍然只能使用如下所示的匿名委托选项。 - John Suit
@JohnSuit,您肯定可以将静态方法添加到“常规”类中。3.0版本后是扩展方法,看起来像实例方法。3.0版本之前的方法不必存在于类上,它可以存在于另一个类上,或者如果您真的需要,可以将该方法设置为实例方法(但您不需要这样做,因为您可以添加静态方法)。 - casperOne
也许我应该为您更好地定义“常规”类。相反,我认为C#语言的官方规范最能说明问题...C# 4.0官方规范,10.6.9 ...“扩展方法只能在非泛型、非嵌套的静态类中声明。扩展方法的第一个参数除this以外不能有其他修饰符,并且参数类型不能是指针类型。” - John Suit
@JohnSuit,还是没有看出问题所在。 有什么阻止你创建一个提供扩展方法的类吗?C#规范没有限制您可以在项目中定义多少个类... - casperOne

11

1)使用匿名委托

private void SetText(string text)
{
    if (this.InvokeRequired)
    {    
        Invoke(new MethodInvoker(delegate() {
            SetText(text);
        }));
    }
    else
    {
        this.textBox1.Text = text;
    }
}

2) AOP方法

[RunInUIThread]
private void SetText(string text)
{
    this.textBox1.Text = text;
}

http://weblogs.asp.net/rosherove/archive/2007/05.aspx?PageIndex=2

3) 使用lambda表达式(由他人概述)。


6

编辑:我应该提到我不认为这是最佳实践

如果您正在使用3.5,您可以创建一个扩展方法,如下:

public static void SafeInvoke(this Control control, Action handler) {
    if (control.InvokeRequired) {
        control.Invoke(handler);
    }
    else {
        handler();
    }
}

以下内容基本上是来自:这里

然后像这样使用它:

textBox1.SafeInvoke(() => .... );

当然,根据您的使用情况修改扩展名等。


2

对大多数人来说,这可能很明显,但是如果您需要检索值,您可以使用已接受的答案并添加另一种方法...

public static T SynchronizedFunc<T>(this ISynchronizeInvoke sync, Func<T> func)
{
    if (!sync.InvokeRequired)
    {
        // Execute the function
        return func();
    }

    // Marshal onto the context
    return (T) sync.Invoke(func, new object[] { });
}

我最近使用了这个来以线程安全的方式获取表单的句柄...
var handle = f.SynchronizedFunc(() => f.Handle);

1
我找到的最简短的解决方案如下所示,它是一个按钮示例,目标是更改按钮的文本。
    if (buttonX.InvokeRequired)
        buttonX.Invoke((Action)(() => buttonX.Text = "Record"));
    else
        buttonX.Text = "Record";

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