Проблема потоков Winforms, второй поток не может получить доступ к 1-м основным элементам управления форм - PullRequest
7 голосов
/ 29 сентября 2008

У меня есть приложение 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 (..), в этом моя проблема. Я бы подумал, что этот код хорош, чтобы пойти!

Забавно в том, что этот код работал очень хорошо в пятницу ???

Ответы [ 10 ]

9 голосов
/ 29 сентября 2008

Вы не можете получить доступ к элементам пользовательского интерфейса из нескольких потоков.

Одним из способов решения этой проблемы является вызов метода Invoke элемента управления с делегатом функции, которая использует элементы пользовательского интерфейса (например, окно сообщения). Что-то вроде:

public delegate void InvokeDelegate();

public void SomeMethod()
{

    button1.Invoke((InvokeDelegate)doUIStuff);


}


void doUIStuff()
{
           MessageBox.Show(this,   
                ex.Message, 
                "Error", 
                MessageBoxButtons.OK, 
                MessageBoxIcon.Error
            );
}
7 голосов
/ 29 сентября 2008

, чтобы избежать межпоточных исключений (InvalidOperationException), вот код шаблона:

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 голосов
/ 29 сентября 2008

Для простоты вы можете изучить использование BackGroundWorker класса. Этот класс обеспечит платформу для обработки ваших потоков и событий уведомлений о прогрессе. Ваш поток пользовательского интерфейса будет обрабатывать событие прогресса и отображать сообщение об ошибке, которое вы передаете обратно.

2 голосов
/ 29 сентября 2008

OR

1 голос
/ 11 октября 2010

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

Общий метод для доступа к многопоточным 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");
1 голос
/ 29 сентября 2008

Правило номер один, когда многопоточность - вы абсолютно не можете касаться пользовательского интерфейса из рабочих потоков. Существует много способов реализации многопоточности, и очень трудно сделать это «правильно».

Вот краткая статья, которая должна вам помочь - Обновление пользовательского интерфейса из вторичного потока

А вот большая статья, в которой подробно обсуждается многопоточность - Многопоточность в .NET

0 голосов
/ 07 января 2010

Мне нравится рекурсивный вызов.

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 голосов
/ 29 сентября 2008

Проверка на InvokeRequired

0 голосов
/ 29 сентября 2008
'*******************************************************************
' 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

Вот что я делаю.

0 голосов
/ 29 сентября 2008

Вы должны НЕ использовать BeginInvoke, вы должны использовать Invoke, а затем, как только вы это поймете, вы можете использовать BeginInvoke, если это действительно необходимо.

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