Кратчайший способ написать потокобезопасный метод доступа к элементу управления Windows Form - PullRequest
25 голосов
/ 21 февраля 2009

В этой статье:

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 ]

34 голосов
/ 21 февраля 2009

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:

У вас нет роскоши лямбда-выражений, но вы все равно можете обобщить код. Это почти то же самое, но не метод расширения:

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; });
}
8 голосов
/ 21 февраля 2009

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) Использование лямбда-выражений (обведено другими). ​​

4 голосов
/ 21 февраля 2009

Редактировать: я должен упомянуть, что не считаю это лучшей практикой

Если вы используете 3.5, вы можете создать метод расширения с эффектом:

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

это в основном взято из: Здесь

Тогда используйте это как:

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

Конечно, измените расширение и т. Д. Для вашего использования.

1 голос
/ 07 января 2015

Это может быть очевидным для большинства, но вы можете взять принятый ответ и добавить другой метод, если вам нужно получить значение ...

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);
0 голосов
/ 16 марта 2018

Самое короткое решение, которое я нашел, показано в примере кнопки ниже, где цель состоит в том, чтобы изменить текст кнопки.

    if (buttonX.InvokeRequired)
        buttonX.Invoke((Action)(() => buttonX.Text = "Record"));
    else
        buttonX.Text = "Record";
...