Какой самый простой способ сделать Cross-Thread Winforms? - PullRequest
1 голос
/ 24 февраля 2009

Чтобы сделать правильный межпотоковый доступ, я использую кусок кода примерно так:

Private Delegate Sub DelSetButton(ByVal button As Button, ByVal label As String, ByVal enabled As Boolean)

Private Sub SetButton(ByVal button As Button, ByVal label As String, ByVal enabled As Boolean)
    If InvokeRequired Then
        Invoke(New DelSetButton(AddressOf SetButton), button, label, enabled)

    Else
        button.Enabled = enabled
        button.Text = label

    End If
End Sub

Что не мило.

  • Мне нужно создать делегата с такой же подписью
  • Мне нужно написать аналогичный вызов для каждого типа элемента управления или каждого действия, которое я хочу

Я использую VB.NET 9 / .NET Framework 3.5.

Есть ли лучший способ сделать это? Или я застрял с этой моделью?

ОБНОВЛЕНИЕ:

После ответов мне больше всего нравится этот:

Private Sub ContSetButton(ByVal button As Button, ByVal label As String, ByVal enabled As Boolean)
    Invoke(New Action(Of Button, String, Boolean)(AddressOf SetButton), button, label, enabled)
End Sub

Private Sub SetButton(ByVal button As Button, ByVal label As String, ByVal Enabled As Boolean)
    button.Text = label
    button.Enabled = Enabled
End Sub

Все еще не идеально, но лучше, чем я делал изначально.

Не стесняйтесь отвечать, если вы можете сделать это лучше, чем это.

Ответы [ 7 ]

3 голосов
/ 24 февраля 2009

Есть много вариантов. Один из них будет использовать SynchronizationContext (извините за мой C #)

SynchronizationContext context = SynchronizationContext.Current;

// Later on
private void SetButton(Button button, string label)
{
    context.Send(delegate 
        {
            button.Text = label;
        }, null);
}

и это почти всё. Переведите анонимных делегатов в VB.NET, и все будет в порядке.

Другой вариант будет состоять в использовании аспектно-ориентированного программирования (AOP) и иметь аспект, который будет перенаправлять определенные вызовы в поток пользовательского интерфейса:

[ExecuteOnUIThread()]
protected virtual void SetButton(Button button, string label)
{
    button.Text = label;
}
3 голосов
/ 24 февраля 2009

(Предполагается, что вам нужен более подробный контроль, чем обеспечивает BackgroundWorker - если этого достаточно для вас, это правильный ответ, как предлагает Мехрдад Афшари.)

Вам не нужно создавать тип делегата. Используйте общие типы Func и Action.

Вместо того, чтобы тестировать InvokeRequired, вы можете захотеть просто иметь два метода - один, который всегда вызывает, а затем метод "Impl" (или любой другой), который всегда вызывается только в потоке пользовательского интерфейса и не делает никакого вызова. Это изменение шаблона, а не новый шаблон, но оно может быть немного чище.

Должна быть возможность написать более общий способ обработки этого, но мне понадобится немного времени, чтобы обдумать это ... и я бы представил это на C #, а не на VB, тоже ...

3 голосов
/ 24 февраля 2009

Вы можете использовать BackgroundWorker и использовать метод ReportProgress для связи с потоком пользовательского интерфейса. В потоке пользовательского интерфейса вы обрабатываете событие ProgressChanged и соответствующим образом обновляете пользовательский интерфейс.

1 голос
/ 19 сентября 2009

В качестве примечания ко всем отличным ответам здесь, возможно, PostSharp может позволить вам вспомнить аспект, который поможет вам.

Таким образом, вы можете написать свой код так:

<InvokeMightBeRequired>
Private Sub SetButton(ByVal button As Button, ByVal label As String, ByVal enabled As Boolean)
    button.Enabled = enabled
    button.Text = label
End Sub

Что добавит необходимый код для обработки вызывающей части.

Обратите внимание, я не знаю, возможно ли это, но я подозреваю, что это так.

0 голосов
/ 28 апреля 2010

Как ответили выше, но мысль даст лямбда-эквивалент

//module parameter - initialise on load
private SynchronizationContext = SynchronizationContext.Current;
//in the method where you want to modify UI control parameters
ctx.Send(state =>
{
    Left = Screen.PrimaryScreen.WorkingArea.Width - Width - 10;
    Top = Screen.PrimaryScreen.WorkingArea.Height - Height;
    SetBounds(Left, Top, Width, Height);
    Opacity = 5;
}, null);

Я знаю, что вы уже ответили, но мысль поделится тем, что я знаю.

0 голосов
/ 18 сентября 2009

Я использую делегаты для анонимных методов в коде, потому что это не требует создания методов вне текущего блока кода (все это знают). Но иногда необходимо использовать параметризованный делегат для устранения дублированного кода, и тогда анонимные методы бесполезны. Я попытался решение, как это:

// somewhere in class body
delegate EventHandler d(ComboBox c1, ComboBox c2);

// in a method (eg. in contructor)
d d1 = delegate(arg1, arg2) // eg. 2 args
{
 return new EventHandler(
  delegate
  {
   // some doing ...   
  }
 );
};

// And then installation of event handler 
combo1.SelectionChangeCommitted += d1(combo1, combo2);

Проблема в том, чтобы вызвать это событие. При использовании именованных методов (например, с помощью клавиши TAB в VS после ввода + =, который генерирует именованный метод, например, combo1_SelectionChangeCommited), всегда есть возможность выполнить: combo1_SelectionChangeCommited(c1, c2).

Для запуска, набранного выше d1, единственное решение, которое я нашел, это класс Фредерика EventExtensions. Это хорошая работа в моей голове. Это означает, что автор находится на более высоком уровне, чем я, в программировании ядра C #. Поэтому я хочу спросить его: Фредерик, могу ли я официально использовать этот код в магистерской работе, над которой я только что работаю? Объявление автора курса фрагмента кода.

0 голосов
/ 24 февраля 2009

Я создал метод расширения для EventHandler и EventHandler, который позволяет безопасно вызывать событие. То есть; обработчик события вызывается, если требуется. Итак, если вы хотите обновить элемент управления Winforms при выполнении какой-либо обработки в другом потоке, вы можете безопасно вызвать событие. Обработчик событий будет вызываться при необходимости.

Это выглядит так (извините за код C #)

public static class EventExtensions
{

    public static void Raise( this EventHandler target, object sender, EventArgs e )
    {
        EventHandler handler = target;

        if( handler != null )
        {
            Delegate[] delegates = handler.GetInvocationList ();

            EventExtensions.Raise (delegates, sender, e);
        }
    }

    public static void Raise<TEventArgs>(this EventHandler<TEventArgs> target, object sender, TEventArgs e ) 
                         where TEventArgs : EventArgs
    {
        EventHandler<TEventArgs> handler = target;

        if( handler != null )
        {
            Delegate[] delegates = handler.GetInvocationList ();

            EventExtensions.Raise (delegates, sender, e);
        }
    }

    private static void Raise( Delegate[] delegates, object sender, EventArgs e )
    {
        foreach( Delegate d in delegates )
        {
            ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;

            if( target != null && target.InvokeRequired )
            {
               target.Invoke (d, new object[] { sender, e });
            }
            else
            {
               d.DynamicInvoke (sender, e);
            }
        }
    }
}

Этот метод расширения позволяет инициировать событие, не беспокоясь о том, требуется ли синхронизация потоков. Просто подними свое мероприятие так:

MyEvent.Raise (this, new MyEventArgs ( ... ));

(у меня также есть перегрузка, которая принимает SynchronizationContext)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...