跨线程Winforms的最简单方法是什么?

时间:2009-02-24 11:22:15

标签: .net winforms multithreading

为了能够进行正确的跨线程访问,我正在使用这样的代码:

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 个答案:

答案 0 :(得分:3)

您可以使用BackgroundWorker并使用ReportProgress方法与UI线程进行通信。在UI线程中,您处理ProgressChanged事件并适当地更新UI。

答案 1 :(得分:3)

(假设你需要比BackgroundWorker更详细的控制 - 如果这对你来说足够了,那就是正确答案,正如Mehrdad Afshari所建议的那样。)

您无需创建委托类型。使用通用的FuncAction类型。

您可能只想拥有两个方法 - 一个总是调用一个方法,而不是一个只在UI线程上调用的“Impl”(或其他)方法,而不是测试InvokeRequired。并且不做任何调用。这是对模式的改变而不是新模式,但它可能会更清晰。

应该可以编写一种更通用的方法来处理这个问题,但是我需要花一点时间来思考它...而且我也会用C#而不是VB来呈现它。 ...

答案 2 :(得分:3)

有很多选择。其中一个是使用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)并且有一个方面可以调整对UI线程的某些调用:

[ExecuteOnUIThread()]
protected virtual void SetButton(Button button, string label)
{
    button.Text = label;
}

答案 3 :(得分:1)

作为对这里所有优秀答案的旁注,也许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

哪个会注入必要的代码来处理调用部分。

注意,我不知道这是否可行,但我怀疑是。

答案 4 :(得分:0)

我在EventHandler和EventHandler上创建了一个扩展方法,允许您以安全的方式引发事件。 那是;如果需要,将调用eventhandler。因此,如果您想在另一个线程上执行某些处理时更新Winforms控件,则可以安全地引发该事件。如有必要,将调用eventhandler。

看起来像这样(抱歉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的重载)

答案 5 :(得分:0)

我在代码中使用委托给匿名方法,因为这不需要在当前代码块之外创建方法(每个人都知道它)。但有时需要使用参数化委托来消除加倍的代码,然后匿名方法是无用的。我试过像这样的解决方案:

// 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);

问题是提出这个事件。当使用命名方法时(例如,在键入+ =生成命名方法,例如。combo1_SelectionChangeCommited后使用VS中的TAB键),总是可以运行:combo1_SelectionChangeCommited(c1, c2)

要在d1上方输入,我找到的一个解决方案是Frederik的班级EventExtensions。 这是我心目中的好工作。这意味着,作者在核心C#编程方面比我高。因此,我想问他:弗雷德里克,我可以在我正在研究的硕士论文中正式使用这段代码吗?宣布代码片段课程的作者。

答案 6 :(得分:0)

如上所述,但是想到会给出lambda等价物

//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);

我知道你已经得到了这个答案,但是想到了我会分享的一点点。