Chyba każdy, kto pisał kiedykolwiek aplikacje wielowątkowe, które swoje wyniki prezentowały na kontrolkach WinForms spotkał się z następującym wyjątkiem:
System.InvalidOperationException occurred, Message="Cross-thread operation not valid: Control 'MainForm' accessed from a thread other than the thread it was created on.", Source="System.Windows.Forms"
Powyższy wyjątek pojawia się, gdy chcemy zmodyfikować zawartość kontrolki z innego wątku, niż ona została wytworzona. W prostszych słowach można powiedzieć, że innego wątku niż ten, który obsługuje okno. Rozwiązanie nie jest może skomplikowane, ale ponieważ musiałem je stosować już wiele razy postanowiłem sobie w tym post'cie przygotować pewnego rodzaju ściągę, a może przyda się to jeszcez komuś.
Otóż w funkcji, która modyfikuje zawartość kontrolki, należy sprawdzić, właściwość o nazwie InvokeRequired i jeżeli jest ona ustawiona na true, to mamy do czynienia z przypadkiem, że funkcję wywołał inny wątek, niż ten co obsługuje kontrolkę. Jeżeli tak jest, to musimy wykorzystać funkcję kontrolki: BeginInvoke, która spowoduje, że to co chcemy wykonać odbędzie się asynchronicznie w stosunku do wątku wywołującego oraz że zostanie, to wykonane w wątku obsługującym kontrolkę.
Poniższy przykład wykorzystuje opisywaną tu metodę i zamykanie okna odbywa się poprzez delegację, jeżeli zachodzi taka potrzeba (InvokeRequired).
void Application_ShutdownRequest( object sender, EventArgs e ) { if ( InvokeRequired ) { BeginInvoke(new MethodInvoker(Close)); } else this.Close(); }
Tez kiedys o tym pisalem: http://tomaszwisniewski.com/watki-c-i-strona-microsoft/
OdpowiedzUsuń:)
no to razem napisaliśmy ;)
OdpowiedzUsuńMożna sobie ułatwić życie, pisząc tak:
OdpowiedzUsuńthis.BeginInvoke(o => o.Close());
Korzystając z rozszerzeń dla klasy Control:
public static class ControlEx
{
public static void Invoke<T>(this T control, Action<T> action) where T : Control
{
if (control.InvokeRequired)
{
control.Invoke(action, new object[] { control });
}
else action(control);
}
public static TResult Invoke<T, TResult>(this T control, Func<T, TResult> function) where T : Control
{
if (control.InvokeRequired)
{
return (TResult)control.Invoke(function, new object[] { control });
}
else return function(control);
}
public static void BeginInvoke<T>(this T control, Action<T> action) where T : Control
{
if (control.InvokeRequired)
{
control.BeginInvoke(action, new object[] { control });
}
else action(control);
}
}
Do wątku głównego możemy się też odwołać za pomocą delegata
OdpowiedzUsuńApplication.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate
{
//Tutaj kod który ma się wykonać w kontekście głównego wątku
}));
Dodam jeszcze, że istnieje już delegat stworzony dokładnie do tego celu: MethodInvoker (http://msdn.microsoft.com/en-us/library/system.windows.forms.methodinvoker.aspx).
OdpowiedzUsuń