Jaki jest najprostszy sposób na aktualizację Label
z innego Thread
?
Mam
Form
biegthread1
i od tego zaczynam inny wątek (thread2
).Natomiast
thread2
przetwarza niektóre pliki Chciałbym aktualizujeLabel
naForm
z aktualnego stanuthread2
„s pracy.
Jak mogłem to zrobić?
c#
.net
multithreading
winforms
user-interface
CruelIO
źródło
źródło
Odpowiedzi:
Dla .NET 2.0, oto kawałek kodu, który napisałem, który robi dokładnie to, co chcesz i działa dla dowolnej właściwości w
Control
:Nazwij to tak:
Jeśli używasz platformy .NET 3.0 lub nowszej, możesz przepisać powyższą metodę jako metodę rozszerzenia
Control
klasy, co uprościłoby wywołanie:AKTUALIZACJA 05/10/2010:
W przypadku .NET 3.0 należy użyć tego kodu:
który wykorzystuje wyrażenia LINQ i lambda, aby umożliwić znacznie czystszą, prostszą i bezpieczniejszą składnię:
Teraz nie tylko nazwa właściwości jest sprawdzana w czasie kompilacji, ale także typ właściwości, więc niemożliwe jest (na przykład) przypisanie wartości ciągu do właściwości boolowskiej, a tym samym spowodowanie wyjątku czasu wykonywania.
Niestety nie powstrzymuje to nikogo przed robieniem głupich rzeczy, takich jak przekazywanie cudzej
Control
własności i wartości, więc następujące elementy z przyjemnością się skompilują:Dlatego dodałem kontrole środowiska wykonawczego, aby upewnić się, że przekazana właściwość faktycznie należy do
Control
metody, która jest wywoływana. Nie idealny, ale wciąż o wiele lepszy niż wersja .NET 2.0.Jeśli ktoś ma jakieś dodatkowe sugestie dotyczące ulepszenia tego kodu w celu zapewnienia bezpieczeństwa podczas kompilacji, prosimy o komentarz!
źródło
SetControlPropertyThreadSafe(myLabel, "Text", status)
nazwać z innego modułu, klasy lub formularzaNajprostszym sposobem jest metoda anonimowe przeszedł do
Label.Invoke
:Zauważ, że
Invoke
blokuje wykonywanie, dopóki się nie zakończy - jest to kod synchroniczny. Pytanie nie dotyczy kodu asynchronicznego, ale w przepełnieniu stosu jest dużo treści na temat pisania kodu asynchronicznego, gdy chcesz się o nim dowiedzieć.źródło
Obsługa długiej pracy
Od wersji .NET 4.5 i C # 5.0 należy używać wzorców asynchronicznych opartych na zadaniach (TAP) wraz z asynchronicznym - czekaj na słowa kluczowe we wszystkich obszarach (w tym GUI):
zamiast modelu asynchronicznego programowania (APM) i asynchronicznego wzorca opartego na zdarzeniu (EAP) (ten ostatni obejmuje klasę BackgroundWorker ).
Następnie zalecane rozwiązanie dla nowego rozwoju to:
Asynchroniczna implementacja modułu obsługi zdarzeń (Tak, to wszystko):
Implementacja drugiego wątku, który powiadamia wątek interfejsu użytkownika:
Zwróć uwagę na następujące:
Dla bardziej gadatliwy przykłady patrz: The Future of C #: Dobre rzeczy przychodzą do tych, którzy „Oczekujcie” przez Josepha Albahari .
Zobacz także koncepcję modelu gwintowania interfejsu użytkownika .
Obsługa wyjątków
Poniższy fragment kodu jest przykładem sposobu obsługi wyjątków i przełączania
Enabled
właściwości przycisku, aby zapobiec wielokrotnym kliknięciom podczas wykonywania w tle.źródło
SecondThreadConcern.LongWork()
zgłasza wyjątek, czy może zostać przechwycony przez wątek interfejsu użytkownika? To jest świetny post, btw.Task.Delay(500).Wait()
? Po co tworzyć zadanie, aby po prostu zablokować bieżący wątek? Nigdy nie należy blokować wątku puli wątków!Odmiana Marc Gravell jest najprostszym rozwiązanie dla .NET 4:
Lub użyj zamiast tego akcji Delegata:
Zobacz tutaj porównanie dwóch: MethodInvoker vs Action for Control.BeginInvoke
źródło
this.refresh()
wymusić unieważnienie i odmalowanie GUI .. jeśli jest to pomocne ..Uruchom i zapomnij metodę rozszerzenia dla .NET 3.5+
Można to wywołać za pomocą następującego wiersza kodu:
źródło
@this
to po prostu nazwa zmiennej, w tym przypadku odniesienie do bieżącej kontrolki wywołującej rozszerzenie. Możesz zmienić nazwę na source lub cokolwiek płynie łodzią. Używam@this
, ponieważ odnosi się do „tego formantu”, który wywołuje rozszerzenie i jest spójny (przynajmniej w mojej głowie) z użyciem słowa kluczowego „to” w normalnym kodzie (bez rozszerzenia).OnUIThread
Zamiast tego nazwałbym tę metodęUIThread
.RunOnUiThread
. Ale to tylko osobisty gust.Jest to klasyczny sposób, w jaki powinieneś to zrobić:
Wątek roboczy zawiera zdarzenie. Wątek interfejsu użytkownika uruchamia inny wątek w celu wykonania pracy i łączy to zdarzenie robocze, aby można było wyświetlić stan wątku roboczego.
Następnie w interfejsie użytkownika musisz krzyżować wątki, aby zmienić rzeczywistą kontrolę ... jak etykieta lub pasek postępu.
źródło
Prostym rozwiązaniem jest użycie
Control.Invoke
.źródło
Kodowanie wątków jest często błędne i zawsze trudne do przetestowania. Nie trzeba pisać kodu wątków, aby zaktualizować interfejs użytkownika z zadania w tle. Wystarczy użyć klasy BackgroundWorker, aby uruchomić zadanie, i jego metody ReportProgress , aby zaktualizować interfejs użytkownika. Zwykle po prostu zgłaszasz procent ukończenia, ale występuje inne przeciążenie, które obejmuje obiekt stanu. Oto przykład, który po prostu zgłasza obiekt typu string:
W porządku, jeśli zawsze chcesz zaktualizować to samo pole. Jeśli masz do wykonania bardziej skomplikowane aktualizacje, możesz zdefiniować klasę reprezentującą stan interfejsu użytkownika i przekazać ją do metody ReportProgress.
I ostatnia rzecz, pamiętaj o ustawieniu
WorkerReportsProgress
flagi, inaczejReportProgress
metoda zostanie całkowicie zignorowana.źródło
backgroundWorker1_RunWorkerCompleted
.Zdecydowana większość odpowiedzi używa,
Control.Invoke
co jest warunkiem wyścigu . Rozważmy na przykład zaakceptowaną odpowiedź:Jeśli użytkownik zamknie formularz tuż przed
this.Invoke
wywołaniem (pamiętaj, żethis
jest toForm
obiekt), anObjectDisposedException
prawdopodobnie zostanie zwolniony.Rozwiązaniem jest użycie
SynchronizationContext
, szczególnieSynchronizationContext.Current
jak sugeruje hamilton.danielb (inne odpowiedzi opierają się na konkretnychSynchronizationContext
implementacjach, co jest całkowicie niepotrzebne). Chciałbym nieco zmodyfikować swój kod używaćSynchronizationContext.Post
zamiastSynchronizationContext.Send
chociaż (jak zwykle nie ma potrzeby wątku pracownika czekać):Pamiętaj, że w .NET 4.0 i nowszych wersjach naprawdę powinieneś używać zadań do operacji asynchronicznych. Zobacz odpowiedź n-san na równoważne podejście oparte na zadaniu (używanie
TaskScheduler.FromCurrentSynchronizationContext
).Wreszcie, w .NET 4.5 i nowszych można również użyć
Progress<T>
(który w zasadzie rejestrujeSynchronizationContext.Current
po jego utworzeniu), jak wykazał Ryszard Dżegan w przypadkach, gdy długo działająca operacja musi uruchamiać kod interfejsu użytkownika podczas pracy.źródło
Musisz upewnić się, że aktualizacja nastąpi we właściwym wątku; wątek interfejsu użytkownika.
Aby to zrobić, musisz wywołać moduł obsługi zdarzeń zamiast wywoływać go bezpośrednio.
Możesz to zrobić, podnosząc swoje wydarzenie w ten sposób:
(Kod jest wpisany tutaj z mojej głowy, więc nie sprawdziłem poprawnej składni itp., Ale powinienem zacząć.)
Pamiętaj, że powyższy kod nie będzie działał w projektach WPF, ponieważ formanty WPF nie implementują
ISynchronizeInvoke
interfejsu.W celu upewnienia się, że powyższy kod prac z Windows Forms i WPF i wszystkich innych platformach, można mieć okiem na
AsyncOperation
,AsyncOperationManager
iSynchronizationContext
klas.Aby łatwo wywoływać zdarzenia w ten sposób, stworzyłem metodę rozszerzenia, która pozwala mi uprościć wywoływanie zdarzenia, po prostu wywołując:
Oczywiście możesz również skorzystać z klasy BackGroundWorker, która wyodrębni tę sprawę za Ciebie.
źródło
Musisz wywołać metodę w wątku GUI. Możesz to zrobić, wywołując Control.Invoke.
Na przykład:
źródło
Z powodu trywialności scenariusza miałbym sondę wątku interfejsu użytkownika dotyczącą statusu. Myślę, że przekonasz się, że może być dość elegancki.
Podejście to pozwala uniknąć operacji zestawiania wymaganej podczas korzystania z
ISynchronizeInvoke.Invoke
iISynchronizeInvoke.BeginInvoke
metod . Nie ma nic złego w stosowaniu techniki marszrutowania, ale należy pamiętać o kilku zastrzeżeniach.BeginInvoke
zbyt często, ponieważ może to spowodować przepełnienie pompy komunikatów.Invoke
wątku roboczego jest połączeniem blokującym. Tymczasowo zatrzyma pracę wykonywaną w tym wątku.Strategia, którą proponuję w tej odpowiedzi, odwraca role komunikacyjne wątków. Zamiast wypychania danych przez wątek roboczy, wątek interfejsu użytkownika sprawdza go. Jest to powszechny wzorzec stosowany w wielu scenariuszach. Ponieważ wszystko, co chcesz zrobić, to wyświetlać informacje o postępie z wątku roboczego, więc myślę, że przekonasz się, że to rozwiązanie jest świetną alternatywą dla rozwiązania zestawiania. Ma następujące zalety.
Control.Invoke
lubControl.BeginInvoke
podejścia, które ściśle je łączy.źródło
Elapsed
zdarzeń, używasz metody członka, dzięki czemu możesz usunąć licznik czasu, gdy formularz jest usuwany ...System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };
polubienie i przypisanie gom_Timer.Elapsed += handler;
później, w kontekście rozporządzania, czym_Timer.Elapsed -= handler;
mam rację? I do pozbywania się / zamykania zgodnie z radą omówioną tutaj .Żadne z elementów Invoke z poprzednich odpowiedzi nie jest konieczne.
Musisz spojrzeć na WindowsFormsSynchronizationContext:
źródło
To jest podobne do powyższego rozwiązania używającego .NET Framework 3.0, ale rozwiązało problem obsługi bezpieczeństwa podczas kompilacji .
Używać:
Kompilator zawiedzie, jeśli użytkownik przekaże nieprawidłowy typ danych.
źródło
Salvete! Po wyszukaniu tego pytania znalazłem odpowiedzi FrankG i Oregon Ghost, które są dla mnie najłatwiejsze i najbardziej przydatne. Teraz piszę w języku Visual Basic i przeprowadziłem ten fragment kodu przez konwerter; więc nie jestem pewien, jak to się potoczy.
Mam okno dialogowe o nazwie o nazwie
form_Diagnostics,
RichtextupdateDiagWindow,
którego używam jako rodzaj wyświetlania dziennika. Musiałem być w stanie zaktualizować jego tekst ze wszystkich wątków. Dodatkowe linie umożliwiają automatyczne przewijanie okna do najnowszych linii.Mogę teraz zaktualizować ekran o jedną linię z dowolnego miejsca w całym programie w sposób, który według ciebie działałby bez wątków:
Kod główny (wstaw to do kodu klasy formularza):
źródło
W wielu celach jest to tak proste:
„serviceGUI ()” to metoda na poziomie GUI w formularzu (this), która może zmieniać dowolną liczbę kontrolek. Wywołaj „updateGUI ()” z drugiego wątku. Parametry można dodawać w celu przekazania wartości lub (prawdopodobnie szybciej) używać zmiennych zasięgu klasy z blokadami na nich zgodnie z wymaganiami, jeśli istnieje możliwość zderzenia między dostępnymi do nich wątkami, co mogłoby spowodować niestabilność. Użyj BeginInvoke zamiast Invoke, jeśli wątek inny niż GUI ma krytyczne znaczenie dla czasu (pamiętając o ostrzeżeniu Briana Gideona).
źródło
To w mojej odmianie Cana 3.0 rozwiązania Iana Kempa:
Nazywasz to tak:
W przeciwnym razie oryginał jest bardzo fajnym rozwiązaniem.
źródło
Zauważ, że
BeginInvoke()
jest to lepsze niżInvoke()
ponieważ jest mniej prawdopodobne, że spowoduje zakleszczenie (jednak nie jest to problemem przy przypisywaniu tekstu do etykiety):Podczas używania
Invoke()
czekasz na powrót metody. Może się zdarzyć, że zrobisz coś w wywoływanym kodzie, który będzie musiał czekać na wątek, co może nie być od razu oczywiste, jeśli jest zakopane w niektórych wywoływanych funkcjach, co samo może nastąpić pośrednio za pośrednictwem procedur obsługi zdarzeń. Będziesz więc czekał na wątek, wątek będzie na ciebie czekał, a ty będziesz zablokowany.To faktycznie spowodowało zawieszenie się części naszego wydanego oprogramowania. Łatwo było to naprawić, zastępując
Invoke()
goBeginInvoke()
. Jeśli nie potrzebujesz operacji synchronicznej, co może mieć miejsce, jeśli potrzebujesz wartości zwracanej, użyjBeginInvoke()
.źródło
Kiedy napotkałem ten sam problem, szukałem pomocy od Google, ale zamiast dać mi proste rozwiązanie, pomyliłem się bardziej, podając przykłady
MethodInvoker
i bla bla bla. Postanowiłem więc rozwiązać to samodzielnie. Oto moje rozwiązanie:Utwórz delegata w ten sposób:
Możesz wywołać tę funkcję w nowym wątku takim jak ten
Nie myl się
Thread(() => .....)
. Podczas pracy na wątku używam anonimowej funkcji lub wyrażenia lambda. Aby zmniejszyć liczbę wierszy kodu, możesz również użyćThreadStart(..)
metody, której nie powinienem tutaj wyjaśniać.źródło
Po prostu użyj czegoś takiego:
źródło
e.ProgressPercentage
, czy nie jesteś już w wątku interfejsu użytkownika z metody, którą wywołujesz?Możesz użyć już istniejącego delegata
Action
:źródło
Moja wersja polega na wstawieniu jednej linii rekurencyjnej „mantry”:
Bez argumentów:
Dla funkcji, która ma argumenty:
TO JEST .
Trochę argumentacji : zwykle źle jest umieścić czytelność kodu po wstawieniu {} po
if ()
wyrażeniu instrukcji w jednym wierszu. Ale w tym przypadku jest to rutynowa „ta sama mantra”. Nie psuje to czytelności kodu, jeśli metoda ta jest spójna w całym projekcie. I to oszczędza twój kod przed śmieceniem (jeden wiersz kodu zamiast pięciu).Jak widać
if(InvokeRequired) {something long}
, po prostu wiesz, że „można bezpiecznie wywoływać tę funkcję z innego wątku”.źródło
Spróbuj odświeżyć etykietę przy użyciu tego
źródło
Utwórz zmienną klasową:
Ustaw go w konstruktorze, który tworzy interfejs użytkownika:
Kiedy chcesz zaktualizować etykietę:
źródło
Musisz użyć invoke i delegować
źródło
Większość innych odpowiedzi na to pytanie jest dla mnie trochę skomplikowana (jestem nowy w C #), więc piszę:
Mam aplikację WPF i zdefiniowałem pracownika, jak poniżej:
Kwestia:
Rozwiązanie:
Nie wiem jeszcze, co oznacza powyższa linia, ale działa.
W przypadku WinForms :
Rozwiązanie:
źródło
Myślę, że najłatwiejszy sposób:
źródło
Na przykład uzyskaj dostęp do kontrolki innej niż w bieżącym wątku:
Tam
lblThreshold
jest Label iSpeed_Threshold
zmienna globalna.źródło
Gdy jesteś w wątku interfejsu użytkownika, możesz zapytać go o harmonogram zadań kontekstu synchronizacji. Dałoby ci TaskScheduler, który planuje wszystko w wątku interfejsu użytkownika.
Następnie możesz połączyć swoje zadania, aby gdy wynik był gotowy, inne zadanie (zaplanowane w wątku interfejsu użytkownika) wybiera je i przypisuje do etykiety.
Działa to w przypadku zadań (nie wątków), które są obecnie preferowanym sposobem pisania współbieżnego kodu .
źródło
Task.Start
zazwyczaj nie jest dobrą praktyką blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspxWłaśnie przeczytałem odpowiedzi i wydaje się to bardzo gorący temat. Obecnie używam .NET 3.5 SP1 i Windows Forms.
Dobrze znana formuła doskonale opisana w poprzednich odpowiedziach, która korzysta z właściwości InvokeRequired, obejmuje większość przypadków, ale nie całą pulę.
Co jeśli uchwyt nie został jeszcze utworzony?
Właściwość InvokeRequired , jak opisano tutaj (odwołanie Control.InvokeRequired Właściwość do MSDN) zwraca wartość true, jeśli wywołanie zostało wykonane z wątku, który nie jest wątkiem GUI, false, jeśli wywołanie zostało wykonane z wątku GUI, lub jeśli uchwyt był jeszcze nie utworzone.
Możesz spotkać wyjątek, jeśli chcesz, aby formularz modalny był pokazywany i aktualizowany przez inny wątek. Ponieważ chcesz, aby ten formularz był wyświetlany modalnie, możesz wykonać następujące czynności:
Delegat może zaktualizować etykietę w GUI:
Może to spowodować InvalidOperationException jeśli operacje przed aktualizacją etykiety „zajmuje mniej czasu” (czytać i interpretować go jako uproszczenie) niż czas potrzebny do wątku GUI aby utworzyć formularz „s uchwyt . Dzieje się tak w ramach metody ShowDialog () .
Powinieneś również sprawdzić uchwyt w ten sposób:
Możesz obsłużyć operację, aby wykonać, jeśli uchwyt nie został jeszcze utworzony: możesz po prostu zignorować aktualizację GUI (jak pokazano w powyższym kodzie) lub możesz poczekać (bardziej ryzykowne). To powinno odpowiedzieć na pytanie.
Opcjonalne rzeczy: Osobiście wymyśliłem kodowanie:
Karmię moje formularze, które są aktualizowane przez inny wątek, instancją tego ThreadSafeGuiCommand i definiuję metody, które aktualizują GUI (w moim formularzu) w następujący sposób:
W ten sposób jestem całkiem pewien, że będę aktualizować GUI niezależnie od tego, który wątek wykona połączenie, opcjonalnie czekając na ściśle określony czas (limit czasu).
źródło