Wywołaj (deleguj)


93

Czy ktoś może wyjaśnić to oświadczenie napisane pod tym linkiem

Invoke(Delegate):

Wykonuje określony delegat w wątku, który jest właścicielem uchwytu okna bazowego kontrolki.

Czy ktoś może wyjaśnić, co to znaczy (zwłaszcza ten odważny). Nie jestem w stanie tego jasno zrozumieć


4
Odpowiedź na to pytanie jest powiązana z właściwością Control.InvokeRequired - patrz msdn.microsoft.com/en-us/library/…
kreska

Odpowiedzi:


131

Odpowiedź na to pytanie dotyczy sposobu działania kontrolek języka C #

Kontrolki w Windows Forms są powiązane z określonym wątkiem i nie są bezpieczne dla wątków. W związku z tym, jeśli wywołujesz metodę kontrolki z innego wątku, musisz użyć jednej z metod wywołania kontrolki, aby zorganizować wywołanie do odpowiedniego wątku. Ta właściwość może służyć do określenia, czy należy wywołać metodę invoke, co może być przydatne, jeśli nie wiesz, który wątek jest właścicielem formantu.

Z Control.InvokeRequired

W efekcie funkcja Invoke zapewnia, że ​​wywoływany kod występuje w wątku, w którym formant „żyje”, skutecznie zapobiegając wyjątkom krzyżowym.

Z historycznego punktu widzenia, w .Net 1.1 było to faktycznie dozwolone. Oznaczało to, że możesz spróbować wykonać kod w wątku „GUI” z dowolnego wątku w tle, a to w większości zadziała. Czasami powodowało to po prostu zamknięcie aplikacji, ponieważ skutecznie przerywałeś wątek GUI, gdy wykonywał coś innego. To jest wyjątek wielowątkowy - wyobraź sobie, że próbujesz zaktualizować TextBox, podczas gdy GUI maluje coś innego.

  • Które działanie ma pierwszeństwo?
  • Czy w ogóle możliwe jest, aby oba miały miejsce jednocześnie?
  • Co dzieje się z pozostałymi poleceniami wymaganymi przez GUI?

Skutecznie przerywasz kolejkę, co może mieć wiele nieprzewidzianych konsekwencji. Invoke jest w rzeczywistości „grzecznym” sposobem umieszczania w kolejce tego, co chcesz zrobić, a ta reguła była wymuszana od .Net 2.0 i dalej przez rzucany InvalidOperationException .

Aby zrozumieć, co faktycznie dzieje się za kulisami i co oznacza „wątek GUI”, warto zrozumieć, czym jest pompa komunikatów lub pętla komunikatów.

W rzeczywistości odpowiedź na to pytanie znajduje się już w pytaniu „ Co to jest pompa komunikatów ” i jest zalecana do przeczytania w celu zrozumienia faktycznego mechanizmu, z którym się łączysz podczas interakcji z kontrolami.

Inne przydatne pozycje to:

Co się dzieje z Begin Invoke

Jedną z głównych zasad programowania GUI systemu Windows jest to, że tylko wątek, który utworzył formant, może uzyskać dostęp i / lub modyfikować jego zawartość (z wyjątkiem kilku udokumentowanych wyjątków). Spróbuj zrobić to z dowolnego innego wątku, a uzyskasz nieprzewidywalne zachowanie, od zakleszczenia, przez wyjątki po w połowie zaktualizowany interfejs użytkownika. Właściwym sposobem zaktualizowania formantu z innego wątku jest wysłanie odpowiedniego komunikatu do kolejki komunikatów aplikacji. Kiedy pompa komunikatów podejmie się wykonania tego komunikatu, formant zostanie zaktualizowany w tym samym wątku, który go utworzył (pamiętaj, że pompa komunikatów działa w wątku głównym).

i, aby uzyskać bardziej rozbudowany przegląd kodu z reprezentatywną próbką:

Nieprawidłowe operacje między wątkami

// the canonical form (C# consumer)

public delegate void ControlStringConsumer(Control control, string text);  // defines a delegate type

public void SetText(Control control, string text) {
    if (control.InvokeRequired) {
        control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text});  // invoking itself
    } else {
        control.Text=text;      // the "functional part", executing only on the main thread
    }
}

Gdy już docenisz InvokeRequired, możesz rozważyć użycie metody rozszerzającej do zawijania tych wywołań. Jest to dobrze omówione w pytaniu o przepełnienie stosu Czyszczenie kodu zaśmieconego z wymaganym wywołaniem .

Istnieje również dalszy opis wydarzeń historycznych, które mogą być interesujące.


68

Formant lub obiekt okna w Windows Forms to po prostu otoka wokół okna Win32 identyfikowanego przez uchwyt (czasami nazywany HWND). Większość czynności wykonywanych z formantem ostatecznie spowoduje wywołanie interfejsu API Win32, które używa tego uchwytu. Uchwyt jest własnością wątku, który go utworzył (zazwyczaj wątek główny) i nie powinien być manipulowany przez inny wątek. Jeśli z jakiegoś powodu musisz coś zrobić z kontrolką z innego wątku, możesz użyćInvoke poprosić główny wątek, aby zrobił to w Twoim imieniu.

Na przykład, jeśli chcesz zmienić tekst etykiety z wątku roboczego, możesz zrobić coś takiego:

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));

Czy możesz wyjaśnić, dlaczego ktoś miałby zrobić coś takiego? this.Invoke(() => this.Enabled = true);Cokolwiek się thisodnosi, jest z pewnością w bieżącym wątku, prawda?
Kyle Delaney

1
@KyleDelaney, obiekt nie jest „w” wątku, a bieżący wątek niekoniecznie jest wątkiem, który utworzył obiekt.
Thomas Levesque

24

Jeśli chcesz zmodyfikować formant, należy to zrobić w wątku, w którym formant został utworzony. Ta Invokemetoda umożliwia wykonywanie metod w skojarzonym wątku (wątku, który jest właścicielem uchwytu okna bazowego kontrolki).

W poniższym przykładzie thread1 zgłasza wyjątek, ponieważ SetText1 próbuje zmodyfikować textBox1.Text z innego wątku. Ale w wątku2 akcja w SetText2 jest wykonywana w wątku, w którym został utworzony TextBox

private void btn_Click(object sender, EvenetArgs e)
{
    var thread1 = new Thread(SetText1);
    var thread2 = new Thread(SetText2);
    thread1.Start();
    thread2.Start();
}

private void SetText1() 
{
    textBox1.Text = "Test";
}

private void SetText2() 
{
    textBox1.Invoke(new Action(() => textBox1.Text = "Test"));
}

Bardzo podoba mi się to podejście, ukrywa naturę delegatów, ale i tak jest fajnym skrótem.
shytikov

7
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });

Użycie System.Action sugerowane przez ludzi w innych odpowiedziach działa tylko na frameworku 3.5+, dla starszych wersji działa to doskonale
Suicide Platypus

2

W praktyce oznacza to, że delegat ma gwarancję wywołania w głównym wątku. Jest to ważne, ponieważ w przypadku kontrolek systemu Windows, jeśli nie zaktualizujesz ich właściwości w głównym wątku, albo nie zobaczysz zmiany, albo kontrolka zgłosi wyjątek.

Wzór to:

void OnEvent(object sender, EventArgs e)
{
   if (this.InvokeRequired)
   {
       this.Invoke(() => this.OnEvent(sender, e);
       return;
   }

   // do stuff (now you know you are on the main thread)
}

2

this.Invoke(delegate)upewnij się, że wywołujesz delegata argument do this.Invoke()głównego wątku / utworzonego wątku.

Mogę powiedzieć, że reguła Thumb nie ma dostępu do twoich formantów formularza z wyjątkiem głównego wątku.

Poniższe wiersze mogą mieć sens przy używaniu Invoke ()

    private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.textBox1.InvokeRequired)
        {   
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.textBox1.Text = text;
        }
    }

Istnieją sytuacje, gdy tworzysz wątek Threadpool (tj. Wątek roboczy), który będzie działał w wątku głównym. Nie utworzy nowego wątku, ponieważ główny wątek jest dostępny do przetwarzania dalszych instrukcji. Więc najpierw sprawdź, czy bieżący wątek jest głównym wątkiem, używając this.InvokeRequiredif zwraca true, że bieżący kod działa w wątku roboczym, więc wywołaj this.Invoke (d, new object [] {text});

w przeciwnym razie bezpośrednio zaktualizuj formant interfejsu użytkownika (tutaj masz gwarancję, że uruchamiasz kod w głównym wątku).


1

Oznacza to, że delegat będzie działał w wątku interfejsu użytkownika, nawet jeśli wywołasz tę metodę z procesu roboczego w tle lub wątku puli wątków. Elementy interfejsu użytkownika mają powinowactwo do wątków - lubią rozmawiać bezpośrednio tylko z jednym wątkiem: wątkiem interfejsu użytkownika. Wątek interfejsu użytkownika jest zdefiniowany jako wątek, który utworzył wystąpienie formantu i dlatego jest skojarzony z uchwytem okna. Ale to wszystko jest szczegółem implementacji.

Kluczowa kwestia jest taka: wywołałbyś tę metodę z wątku roboczego, aby uzyskać dostęp do interfejsu użytkownika (aby zmienić wartość w etykiecie itp.) - ponieważ nie możesz tego zrobić z żadnego innego wątku niż wątek interfejsu użytkownika.


0

Delegaty są zasadniczo wbudowane Actionlub Func<T>. Możesz zadeklarować delegata poza zakresem metody, którą uruchamiasz lub używając lambdaexpression ( =>); ponieważ uruchamiasz delegata w ramach metody, uruchamiasz go w wątku, który jest uruchamiany dla bieżącego okna / aplikacji, który jest pogrubiony.

Przykład Lambda

int AddFiveToNumber(int number)
{
  var d = (int i => i + 5);
  d.Invoke(number);
}

0

Oznacza to, że przekazany delegat jest wykonywany w wątku, który utworzył obiekt Control (który jest wątkiem interfejsu użytkownika).

Musisz wywołać tę metodę, gdy aplikacja jest wielowątkowa i chcesz wykonać jakąś operację interfejsu użytkownika z wątku innego niż wątek interfejsu użytkownika, ponieważ jeśli po prostu spróbujesz wywołać metodę na kontrolce z innego wątku, otrzymasz System.InvalidOperationException.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.