2008-09-29 7 views
7

Mam aplikację WinForm, problem związany jest z wątkami. Ponieważ nazywam "MyCustomCode(), który tworzy nowy wątek i wywołuje metodę " SomeMethod() ", który następnie dostęp do MessageBox.Show (...).Problem z nawijaniem WinForm, drugi wątek nie może uzyskać dostępu do 1. głównych form kontroli

Problem dotyczy wątkowania, ponieważ nowo utworzony wątek próbuje uzyskać dostęp do formantu utworzonego w innym wątku.

otrzymuję błąd:

pracy Cross wątek nie ważne: kontrola „TestForm” obejrzano z wątku innego niż wątek został utworzony na.

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(); 
} 

Aktualizacja

Jeśli spojrzeć na ten przykład http://www.codeproject.com/KB/cs/vanillaupdaterblock.aspx metoda CheckAndUpdate dzwoni MessageBox.Show (..) to właśnie mój problem. Pomyślałbym, że kod jest dobry!

Zabawna rzecz czy ten kod działał dobrze w piątek ???

+0

Czy to możliwe, ponieważ zainstalowałem .net 3.5? Czy to jest funkcja "3.5"? Wątpię, ale to jedyne wytłumaczenie! –

+0

(Zainstalowałem go ostatnio ..) –

Odpowiedz

9

Nie można uzyskać dostępu do elementów interfejsu z wielu wątków.

Jednym ze sposobów rozwiązania tego problemu jest wywołanie metody Invoke formantu z delegatem do funkcji, która wykorzystuje elementy interfejsu użytkownika (np. Pole komunikatu). Coś jak:

public delegate void InvokeDelegate(); 

public void SomeMethod() 
{ 

    button1.Invoke((InvokeDelegate)doUIStuff); 


} 


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

Należy sprawdzić, czy wywołanie jest wymagane, np. button1.InvokeRequired. – RickL

+0

A jeśli uchwyt formularza nie został jeszcze utworzony, InvokeRequired zawsze zwróci false. Dlatego właśnie zaleca się SynchronizationContext. –

0

Należy NIE użycie BeginInvoke, należy użyć Invoke, następnie raz pojąć, że można spojrzeć na użyciu BeginInvoke jeśli naprawdę potrzebne.

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() 

Następnie w aplikacji macierzystej masz:

'************************************************************************* 
'  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 

To właśnie robię.

+2

WTF? Co to jest? – leppie

+0

Używa kolejki komunikatów zdarzeń do obsługi komunikacji między procesami (wątek do rodzica w tym przypadku. :-) Mam "nieznaną liczbę" wątków, wszystkie wysyłające aktualizacje do tego samego okna nadrzędnego. –

+0

Jestem w zgodzie z leppie. – RickL

7

uniknąć wyjątków cross-wątek (InvalidOperationException), oto wzór Kod:

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 
} 

Zgadzam się, że jest to irytujące, ale jest to artefakt z faktu, że kontrole Windows GUI nie są thread- bezpieczny. Wyjątek może być wyłączony z flagą gdzieś lub w innym miejscu, ale nie rób tego, ponieważ może to doprowadzić do bardzo trudnego znalezienia błędów.

1

Zasada numer jeden, gdy wielowątkowość jest absolutnie nie może dotknąć interfejsu użytkownika z wątków roboczych. Istnieje wiele sposobów implementacji wielowątkowości i bardzo trudno jest ją "poprawić".

Oto zwięzły artykuł, który powinien pomóc - Updating the UI from a Secondary Thread

A oto długi artykuł, który omawia gwintowania pogłębione - Multi-threading in .NET

3

Aby zachować rzeczy proste, można spojrzeć w użyciu klasy BackGroundWorker. Ta klasa zapewni ramy do obsługi zdarzeń powiadamiania o wątkach i postępach. Twój wątek Ui obsłuży zdarzenie progress i wyświetli komunikat o błędzie, który odesłałeś.

0

Sprawdź InvokeRequired

0

I tak jak wywołanie rekursywne.

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 
       ); 
     } 
    } 
1

Wiem, że jest to starszy post, ale niedawno znalazłem eleganckie rozwiązanie tego problemu za pomocą metod generycznych i rozszerzeń. Jest to połączenie prac autorów i kilku komentarzy.

rodzajowe Sposób WinForm Trasy gwint dostępu

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

Może to być nazywane w ten sposób, dla uproszczenia, że ​​stosuje się znacznik.

someLabel.Manipulate(lbl => lbl.Text = "Something"); 
Powiązane problemy