Mam scenariusz (Windows Forms, C #, .NET)
- Istnieje główna forma, która obsługuje kontrolę użytkowników.
- Kontrola użytkownika wykonuje pewne ciężkie operacje na danych, tak że jeśli bezpośrednio wywołam
UserControl_Load
metodę, interfejs użytkownika przestanie odpowiadać na czas wykonywania metody ładowania. - Aby temu zaradzić, ładuję dane do innego wątku (próbując jak najmniej zmienić istniejący kod)
- Użyłem wątku procesu roboczego w tle, który będzie ładował dane, a po zakończeniu powiadomi aplikację, że wykonała swoją pracę.
- Teraz przyszedł prawdziwy problem. Cały interfejs użytkownika (formularz główny i jego potomne kontrolki użytkownika) został utworzony w głównym wątku głównym. W metodzie LOAD kontrolera użytkownika pobieram dane na podstawie wartości niektórych formantów (takich jak pole tekstowe) na userControl.
Pseudokod wyglądałby tak:
KOD 1
UserContrl1_LoadDataMethod()
{
if (textbox1.text == "MyName") // This gives exception
{
//Load data corresponding to "MyName".
//Populate a globale variable List<string> which will be binded to grid at some later stage.
}
}
Wyjątkiem było
Nieprawidłowa operacja krzyżowa: dostęp uzyskany z wątku innego niż wątek, w którym został utworzony.
Aby dowiedzieć się więcej na ten temat, zrobiłem trochę googlowania i pojawiła się sugestia, jak użycie następującego kodu
KOD 2
UserContrl1_LoadDataMethod()
{
if (InvokeRequired) // Line #1
{
this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
return;
}
if (textbox1.text == "MyName") // Now it wont give an exception
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be binded to grid at some later stage
}
}
ALE ALE ALE ... wygląda na to, że wróciłem do pierwszego. Aplikacja ponownie przestaje odpowiadać. Wydaje się, że jest to spowodowane wykonaniem linii nr 1, jeśli warunek. Zadanie ładowania jest ponownie wykonywane przez wątek nadrzędny, a nie trzeci odrodzony.
Nie wiem, czy postrzegałem to dobrze, czy źle. Jestem nowy w wątkach.
Jak to rozwiązać, a także jaki jest efekt wykonania linii nr 1, jeśli blok?
Sytuacja jest następująca : chcę załadować dane do zmiennej globalnej na podstawie wartości kontrolki. Nie chcę zmieniać wartości kontrolki z wątku potomnego. Nie zrobię tego nigdy z dziecięcego wątku.
Tak więc dostęp tylko do wartości, aby odpowiednie dane mogły zostać pobrane z bazy danych.
źródło
Odpowiedzi:
Zgodnie z komentarzem do aktualizacji Preraka K (od momentu usunięcia):
Rozwiązanie, które chcesz, powinno wyglądać następująco:
Wykonaj poważne przetwarzanie w osobnym wątku, zanim spróbujesz wrócić do wątku kontrolki. Na przykład:
źródło
Model gwintowania w interfejsie użytkownika
Przeczytaj Model wątków w aplikacjach interfejsu użytkownika, aby zrozumieć podstawowe pojęcia. Link prowadzi do strony opisującej model wątków WPF. Jednak Windows Forms wykorzystuje ten sam pomysł.
Wątek interfejsu użytkownika
Metody BeginInvoke i Invoke
Odwołać się
BeginInvoke
Rozwiązanie kodu
Przeczytaj odpowiedzi na pytanie Jak zaktualizować GUI z innego wątku w C #? . Zalecane rozwiązanie dla C # 5.0 i .NET 4.5 znajduje się tutaj .
źródło
Chcesz użyć
Invoke
lubBeginInvoke
tylko minimalnej ilości pracy wymaganej do zmiany interfejsu użytkownika. Twoja „ciężka” metoda powinna zostać wykonana w innym wątku (np. ViaBackgroundWorker
), ale następnie użyjControl.Invoke
/Control.BeginInvoke
just, aby zaktualizować interfejs użytkownika. W ten sposób Twój wątek interfejsu użytkownika będzie mógł obsługiwać zdarzenia interfejsu użytkownika itp.Zobacz mój artykuł na temat wątków dla przykładu WinForms - chociaż artykuł został napisany przed
BackgroundWorker
przyjazdem na scenę i obawiam się, że nie zaktualizowałem go pod tym względem.BackgroundWorker
jedynie nieco upraszcza oddzwanianie.źródło
Wiem, że jest już za późno. Jednak nawet dzisiaj, jeśli masz problemy z dostępem do kontroli wątków? To jest najkrótsza odpowiedź do dnia: P
W ten sposób uzyskuję dostęp do dowolnej kontroli formularza z wątku.
źródło
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
. Rozwiązałem go tutajMiałem ten problem z
FileSystemWatcher
i stwierdziłem, że następujący kod rozwiązał problem:fsw.SynchronizingObject = this
Następnie formant używa bieżącego obiektu formularza do obsługi zdarzeń i dlatego będzie w tym samym wątku.
źródło
.SynchronizingObject = Me
Uważam, że kod sprawdzania i wywoływania, który musi być zaśmiecony we wszystkich metodach związanych z formularzami, jest zbyt szczegółowy i niepotrzebny. Oto prosta metoda rozszerzenia, która pozwala całkowicie ją zlikwidować:
A potem możesz po prostu to zrobić:
Nigdy więcej bałaganu - proste.
źródło
t
w tym przypadku będzietextbox1
- przekazany jako argumentFormanty w .NET nie są na ogół bezpieczne dla wątków. Oznacza to, że nie powinieneś uzyskiwać dostępu do kontrolki z wątku innego niż ten, w którym ona żyje. Aby obejść ten problem, musisz wywołać kontrolkę, co próbuje druga próbka.
Jednak w twoim przypadku wszystko, co zrobiłeś, to przekazać długoterminową metodę z powrotem do głównego wątku. Oczywiście to nie tak naprawdę chcesz robić. Musisz przemyśleć to trochę, aby wszystko, co robisz w głównym wątku, ustawiało szybką właściwość tu i tam.
źródło
Najczystszym (i właściwym) rozwiązaniem problemów związanych z wielowątkowością interfejsu użytkownika jest użycie SynchronizationContext, zobacz Synchronizacja wywołań interfejsu użytkownika w artykule na temat wielowątkowej aplikacji , wyjaśnia to bardzo dobrze.
źródło
Nowy wygląd za pomocą Async / Await i wywołań zwrotnych. Potrzebujesz tylko jednego wiersza kodu, jeśli zachowasz metodę rozszerzenia w swoim projekcie.
Możesz dodać inne rzeczy do metody Extension, takie jak zawijanie jej w instrukcji Try / Catch, pozwalając dzwoniącemu powiedzieć mu, jaki typ zwrócić po zakończeniu, wyjątek oddzwanianie do dzwoniącego:
Dodanie Try Catch, automatyczne rejestrowanie wyjątków i CallBack
źródło
To nie jest zalecany sposób rozwiązania tego błędu, ale można go szybko ukryć, wykona zadanie. Wolę to w przypadku prototypów lub wersji demonstracyjnych. Dodaj
w
Form1()
konstruktorze.źródło
Postępuj zgodnie z najprostszym (moim zdaniem) sposobem modyfikowania obiektów z innego wątku:
źródło
Musisz spojrzeć na przykład Backgroundworker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx Zwłaszcza sposób interakcji z warstwą interfejsu użytkownika. W oparciu o Twój post wydaje się to odpowiadać na Twoje problemy.
źródło
Potrzebowałem tego podczas programowania kontrolera aplikacji monotouch na telefony z iOS w studiu wizualnym prototypowego projektu winforms poza Xamarin stuidio. Woląc programować w VS niż w Xamarin Studio, chciałem, aby kontroler był całkowicie oddzielony od szkieletu telefonu. W ten sposób wdrożenie tego dla innych platform, takich jak Android i Windows Phone, byłoby znacznie łatwiejsze do wykorzystania w przyszłości.
Chciałem rozwiązania, w którym GUI może reagować na zdarzenia bez obciążenia związanego z przełączaniem kodu przełączania wątków za każdym kliknięciem przycisku. Zasadniczo pozwól, aby kontroler klasy zajął się tym, aby kod klienta był prosty. Mógłbyś mieć wiele zdarzeń w GUI, tak jakbyś mógł poradzić sobie w jednym miejscu w klasie byłoby czystsze. Nie jestem ekspertem od wielu teorii. Daj mi znać, jeśli jest to wadliwe.
Formularz GUI nie jest świadomy, że kontroler wykonuje zadania asynchroniczne.
źródło
Oto alternatywny sposób, jeśli obiekt, z którym pracujesz, nie ma
Jest to przydatne, jeśli pracujesz z formularzem głównym w klasie innej niż formularz główny z obiektem, który jest w formie głównej, ale nie ma InvokeRequired
Działa tak samo jak powyżej, ale jest to inne podejście, jeśli nie masz obiektu z wywołaniem invokere, ale masz dostęp do MainForm
źródło
Wzdłuż tych samych wierszy, co poprzednie odpowiedzi, ale bardzo krótki dodatek, który pozwala korzystać ze wszystkich właściwości Control bez wyjątku wywołania wątku krzyżowego.
Metoda pomocnicza
Przykładowe użycie
źródło
źródło
Na przykład, aby pobrać tekst z kontrolki wątku interfejsu użytkownika:
źródło
To samo pytanie: how-to-update-the-gui-from-another-thread-in-c
Dwie drogi:
Zwróć wartość w e.result i użyj jej do ustawienia wartości pola tekstowego w zdarzeniu backgroundWorker_RunWorkerCompleted
Zadeklaruj pewną zmienną, aby przechowywać tego rodzaju wartości w osobnej klasie (która będzie działać jako posiadacz danych). Utwórz statyczną instancję tej klasy i możesz uzyskać do niej dostęp w dowolnym wątku.
Przykład:
źródło
Po prostu użyj tego:
źródło
Działanie y; // zadeklarowany w klasie
label1.Invoke (y = () => label1.Text = "tekst");
źródło
Prosty i wielokrotnego użytku sposób na obejście tego problemu.
Metoda przedłużenia
Przykładowe użycie
źródło
Istnieją dwie opcje operacji krzyżowych.
a drugi to użycie
Control.InvokeRequired jest użyteczny tylko wtedy, gdy działające kontrolki odziedziczone z klasy Control, podczas gdy SynchronizationContext może być używany w dowolnym miejscu. Niektóre przydatne informacje to następujące linki
Interfejs aktualizacji wielowątkowej | .Netto
Interfejs aktualizacji wielowątkowej za pomocą SynchronizationContext | .Netto
źródło