Aktualizacja kontrolki z innego wątku, invoke oraz metody rozszerzające

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.