Czy otrzymałeś kiedyś taki oto błąd?
The calling thread cannot access this object because a different thread owns it.
Dzieje się tak, najczęściej wtedy, gdy próbujemy aktualizować kontrolki użytkownika z innego wątku. Taka czynność nigdy nie była dobrym pomysłem i trzeba było o tym pamiętać, jednak od wersji bodajże 2 .net Framework-a dostajemy taki wyjątek jak powyżej. Dzięki temu, nawet jeśli coś zostanie przeoczone i będziemy jednak chcieli wykonać aktualizację kontrolki użytkownika z innego wątku niż wątek interfejsu, wówczas dostaniemy ładny i zgrabny wyjątek.
Zatem jak zaktualizować kontrolkę użytkownika z innego wątku? Należy posłużyć się motodą Invoke, która wywołuje metodę w wątku właściwym dla wykonywanej operacji. W aplikacji gdzie często wykonujemy tego typu operacje, warto wykorzystać metody rozszerzające, dzięki którym możemy dodać własny plumbing do istniejących klas (nawet systemowych).
I tak w desktop info jest klasa ControlExtensions:
public static class ControlExtensions
{
public static void InvokeIfRequired(this Control control, Action action)
{
if (System.Threading.Thread.CurrentThread != control.Dispatcher.Thread)
{
control.Dispatcher.Invoke(action);
}
else
{
action();
}
}
public static void InvokeIfRequired<T>(this Control control, Action<T> action, T parameter)
{
if (System.Threading.Thread.CurrentThread != control.Dispatcher.Thread)
control.Dispatcher.Invoke(action, parameter);
else
action(parameter);
}
}
Definiuje ona dwie metody rozszerzające (w parametrach metody pojawia się nagle tajemnicze słowo this) InvokeIfRequired. Dzięki nim mogę wywoływać ją bez potrzeby nadmiernego pisania kodu w każdym miejscu, które wymaga lub potencjalnie wymaga invoke-a
Jak z takiego czegoś korzystać?
this.InvokeIfRequired((value) => seconds.Content = value, DateTime.Now.Second.ToString("00"));
W metodzie obsługującej zdarzenie Elapsed dla timera znajduje się powyższy kod, który wpisuje ilość sekund do kontrolki seconds. Proste i zgrabne.