Winforms线程问题,第二个线程无法访问第一个主窗体的控件。

7
我有一个WinForms应用程序,问题与线程有关。因为我调用了'MyCustomCode()'来创建一个新的线程,并调用了方法'SomeMethod()',然后访问MessageBox.Show(...)。
问题与线程有关,因为新创建的线程试图访问在另一个线程上创建的控件。
我收到以下错误消息:
跨线程操作无效: 从非创建控件的线程访问控件'TestForm'。
public TestForm()
{
    InitializeComponent();


    // custom code
    //
    MyCustomCode();


}

public void SomeMethod()
{

    // ***** This causes an error  ****

    MessageBox.Show(this,   
        ex.Message, 
        "Error", 
        MessageBoxButtons.OK, 
        MessageBoxIcon.Error
    );
}



private void InitializeAutoUpdater()
{
        // Seperate thread is spun to keep polling for updates
        ThreadStart ts = new ThreadStart(SomeMethod);
        pollThread = new Thread(ts);
        pollThread.Start();
}

更新

如果你看一下这个例子 http://www.codeproject.com/KB/cs/vanillaupdaterblock.aspx,方法CheckAndUpdate中调用了MessageBox.Show(..),这就是我的问题所在。我本以为代码可以正常运行!

有趣的事情是这段代码上星期五还能够正常工作???


这可能是因为我安装了 .net 3.5 吗?这是一个 3.5 的“特性”吗?我怀疑,但这是唯一的解释! - Anonymous Code
这个回答解决了你的问题吗?跨线程操作无效:从创建它的线程以外的线程访问控件 - Peter Duniho
这个回答解决了你的问题吗?如何从另一个线程更新GUI? - Peter Duniho
9个回答

9

无法从多个线程访问UI元素。

解决这个问题的一种方法是通过使用控件的Invoke方法,将委托给使用UI元素的函数(如消息框)。类似于:

public delegate void InvokeDelegate();

public void SomeMethod()
{

    button1.Invoke((InvokeDelegate)doUIStuff);


}


void doUIStuff()
{
           MessageBox.Show(this,   
                ex.Message, 
                "Error", 
                MessageBoxButtons.OK, 
                MessageBoxIcon.Error
            );
}

你应该检查是否需要调用,例如:button1.InvokeRequired。 - RickL
如果表单的句柄还没有创建,InvokeRequired总是返回false。这就是为什么更建议使用SynchronizationContext的原因。 - Romain Verdier

7
为了避免跨线程异常(InvalidOperationException),以下是代码模式:
```csharp private readonly object _locker = new object(); public void DoSomething() { lock (_locker) { // do something here } } ```
请注意,这里使用了锁定机制以确保在同一时间只有一个线程可以访问该代码块。
protected delegate void someGuiFunctionDelegate(int iParam);

protected void someGuiFunction(int iParam)
{
    if (this.InvokeRequired)
    {
        someGuiFunctionDelegate dlg = new 
            someGuiFunctionDelegate(this.someGuiFunction);
        this.Invoke(dlg, new object[] { iParam });
        return;
    }

    //do something with the GUI control here
}

我同意这很烦人,但这是由于Windows GUI控件不是线程安全的事实所导致的。异常可以通过某个标志关闭,但不要这样做,因为它可能会导致非常难以找到的错误。


3
为了保持简单,您可以考虑使用BackGroundWorker类。该类将提供一个框架来处理线程和进度通知事件。您的UI线程将处理进度事件并显示您传回的错误消息。

2

1

我知道这是一个较旧的帖子,但最近我使用泛型和扩展方法找到了一个优雅的解决方案来解决这个问题。这是作者作品和一些评论的结合。

跨线程Winforms访问的通用方法

http://www.codeproject.com/KB/cs/GenericCrossThread.aspx

public static void Manipulate<T>(this T control, Action<T> action) where T : Control
{
    if (control.InvokeRequired)
    {
        control.Invoke(new Action<T, Action<T>>(Manipulate),
                    new object[] { control, action });
    }
    else
    { action(control); }
}

可以通过以下方式调用,为了简单起见,我使用了一个标签。

someLabel.Manipulate(lbl => lbl.Text = "Something");

0

我特别喜欢递归调用。

public delegate void InvokeDelegate(string errMessage); 

    public void SomeMethod() 
    { 
        doUIStuff("my error message");
    } 


    void doUIStuff(string errMessage) 
    { 
        if (button1.InvokeRequired)
            button1.Invoke((InvokeDelegate)doUIStuff(errMessage)); 
        else
        {
               MessageBox.Show(this,    
                    ex.Message,  
                    errMessage,  
                    MessageBoxButtons.OK,  
                    MessageBoxIcon.Error 
                ); 
        }
    } 

0

你不应该使用BeginInvoke,而是使用Invoke,一旦你掌握了这一点,如果真的需要,可以考虑使用BeginInvoke。


0
'*******************************************************************
' Get a new processor and fire it off on a new thread.
'*******************************************************************
fpProc = New Processor(confTable, paramFile, keyCount)
AddHandler fpProc.LogEntry, AddressOf LogEntry_Handler
Dim myThread As System.Threading.Thread = New System.Threading.Thread(AddressOf fpProc.ProcessEntry)
myThread.Start()

然后在父应用程序中,您有:

'*************************************************************************
'     Sub: LogEntry_Handler()
'  Author: Ron Savage
'    Date: 08/29/2007
'
' This routine handles the LogEntry events raised by the Processor class
' running in a thread.
'*************************************************************************
Private Sub LogEntry_Handler(ByVal logLevel As Integer, ByVal logMsg As String) Handles fProc.LogEntry
 writeLogMessage(logMsg);
End Sub

这就是我所做的。


它使用事件消息队列来处理进程间通信(在这种情况下是线程到父级)。:-) 我有一个“未知数量”的线程,都向同一个父窗口发送更新。 - Ron Savage
我同意leppie的观点。 - RickL

0

检查 InvokeRequired


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