Automatyzacja wzorca kodu InvokeRequired


179

Uświadomiłem sobie boleśnie, jak często trzeba pisać następujący wzorzec kodu w kodzie GUI sterowanym zdarzeniami, gdzie

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

staje się:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Jest to niezręczny wzór w języku C #, zarówno do zapamiętania, jak i do pisania. Czy ktoś wymyślił jakiś skrót lub konstrukcję, która do pewnego stopnia automatyzuje to? Byłoby fajnie, gdyby istniał sposób na dołączenie funkcji do obiektów, które wykonują tę kontrolę, bez konieczności przechodzenia przez całą tę dodatkową pracę, na przykład object1.InvokeIfNecessary.visible = trueskrótu typu.

Poprzednie odpowiedzi omawiały niepraktyczność za każdym razem wywoływania Invoke (), a nawet wtedy składnia Invoke () jest zarówno nieefektywna, jak i nadal niezręczna.

Czy ktoś wymyślił jakieś skróty?


2
Zastanawiałem się nad tym samym, ale w odniesieniu do Dispatcher.CheckAccess () WPF.
Taylor Leese

Wymyśliłem raczej szaloną sugestię zainspirowaną twoją object1.InvokeIfNecessary.Visible = truelinią; sprawdź moją zaktualizowaną odpowiedź i daj mi znać, co myślisz.
Dan Tao

1
Dodaj Snippet, aby pomóc wdrożyć metodę sugerowaną przez Matta Davisa: zobacz moją odpowiedź (późno, ale pokazuję, jak później czytać ;-))
Aaron Gage

3
Nie rozumiem, dlaczego Microsoft nie zrobił nic, aby to uprościć w .NET. Tworzenie delegatów dla każdej zmiany w formularzu z wątku jest naprawdę denerwujące.
Kamil

@Kamil Nie mogłem się więcej zgodzić! To taki nadzór, biorąc pod uwagę jego wszechobecność. W ramach, po prostu obsłuż wątki, jeśli to konieczne. Wydaje się oczywiste.
SteveCinq

Odpowiedzi:


138

Podejście Lee można jeszcze bardziej uprościć

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

I można tak nazwać

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

Nie ma potrzeby przekazywania kontroli jako parametru do delegata. C # automatycznie tworzy zamknięcie .


AKTUALIZACJA :

Według kilku innych plakatów Controlmożna uogólnić jako ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnott zauważył, że w przeciwieństwie do tego Control do ISynchronizeInvokeinterfejsu wymaga tablicy obiektów dla Invokemetody jako listy parametrów dla metody action.


AKTUALIZACJA 2

Edycje sugerowane przez Mike'a de Klerka (patrz komentarz w pierwszym fragmencie kodu wstawiania punktu):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

Zobacz uwagi ToolmakerSteve poniżej, aby uzyskać obawy dotyczące tej sugestii.


2
Nie byłoby lepiej mieć ISynchronizeInvokezamiast tego Control? (Uznanie dla Jona Skeeta stackoverflow.com/questions/711408/... )
Odys,

@odyodyodys: Dobra uwaga. Nie wiedziałam o tym ISynchronizeInvoke. Ale jedynym typem, który z niego wywodzi (według Reflectora) jest Control, więc przewaga jest ograniczona.
Olivier Jacot-Descombes

3
@ mike-de-clerk, jestem zaniepokojony twoją sugestią dodania while (!control.Visible) ..sleep... Dla mnie ma nieprzyjemny zapach kodu, ponieważ jest to potencjalnie nieograniczone opóźnienie (w niektórych przypadkach może nawet nieskończona pętla), w kodzie, który może zawierać osoby dzwoniące, które nie oczekują takiego opóźnienia (lub nawet impasu). IMHO, każde użycie Sleeppowinno być obowiązkiem każdego dzwoniącego, LUB powinno być w osobnym opakowaniu, które jest wyraźnie oznaczone pod względem konsekwencji. IMHO, zwykle lepiej byłoby „mocno zawieść” (wyjątek, złapać podczas testowania) lub „nic nie robić”, jeśli kontrola nie jest gotowa. Komentarze?
ToolmakerSteve

1
@ OlivierJacot-Descombes, Byłoby wspaniale, gdybyś wyjaśnił, w jaki sposób działa thread.invokerequired?
Sudhir.net,

1
InvokeRequiredinformuje, czy wątek wywołujący różni się od wątku, który utworzył formant. Invokeprzekazuje akcję z wątku wywołującego do wątku kontrolki, w którym jest wykonywana. Zapewnia to, na przykład, że obsługa zdarzenia kliknięcia nigdy nie jest przerywana.
Olivier Jacot-Descombes

133

Możesz napisać metodę rozszerzenia:

public static void InvokeIfRequired(this Control c, Action<Control> action)
{
    if(c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

I użyj tego w ten sposób:

object1.InvokeIfRequired(c => { c.Visible = true; });

EDYCJA: Jak zauważył Simpzon w komentarzach, możesz również zmienić podpis na:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control

Może jestem zbyt głupi, ale ten kod się nie skompiluje. Więc naprawiłem to tak, jak zostało zbudowane przeze mnie (VS2008).
Oliver,

5
Dla kompletności: w WPF istnieje inny mechanizm wysyłania, ale działa raczej analogicznie. Możesz użyć tej metody rozszerzenia tam: public static void InvokeIfRequired <T> (ten T aTarget, akcja <T> aActionToExecute) gdzie T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.

1
Dodałem odpowiedź, która nieco upraszcza rozwiązanie Lee.
Olivier Jacot-Descombes

Cześć, ponieważ gdy używam czegoś podobnego, może istnieć duży problem pochodzący z tej ogólnej implementacji. Jeśli formant jest rozmieszczany / usuwany, otrzymasz wyjątek ObjectDisposedException.
Offler

1
@ Offler - Cóż, jeśli są one rozmieszczone w innym wątku, masz problem z synchronizacją, nie jest to problemem w tej metodzie.
Lee

33

Oto formularz, którego używałem w całym kodzie.

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

Oparłem to na wpisie na blogu tutaj . Nie miałem takiego podejścia, które mnie zawiodło, więc nie widzę powodu, aby komplikować mój kod sprawdzeniem InvokeRequiredwłaściwości.

Mam nadzieję że to pomoże.


+1 - natknąłem się na ten sam wpis na blogu, który zrobiłeś, i myślę, że jest to najczystsze podejście ze wszystkich proponowanych
Tom Bushell

3
Przy takim podejściu występuje niewielki spadek wydajności, który może się nakładać przy wielokrotnym wywołaniu. stackoverflow.com/a/747218/724944
surfuje

4
Musisz użyć, InvokeRequiredjeśli kod mógł zostać wykonany przed wyświetleniem formantu lub wystąpi krytyczny wyjątek.
56ka

9

Utwórz plik ThreadSafeInvoke.snippet, a następnie możesz po prostu wybrać instrukcje aktualizacji, kliknąć prawym przyciskiem myszy i wybrać „Surround With ...” lub Ctrl-K + S:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>ThreadsafeInvoke</Title>
    <Shortcut></Shortcut>
    <Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
    <SnippetTypes>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Code Language="CSharp">
      <![CDATA[
      Invoke( (MethodInvoker) delegate
      {
          $selected$
      });      
      ]]>
    </Code>
  </Snippet>
</CodeSnippet>

6

Oto ulepszona / połączona wersja odpowiedzi Lee, Olivera i Stephana.

public delegate void InvokeIfRequiredDelegate<T>(T obj)
    where T : ISynchronizeInvoke;

public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
    where T : ISynchronizeInvoke
{
    if (obj.InvokeRequired)
    {
        obj.Invoke(action, new object[] { obj });
    }
    else
    {
        action(obj);
    }
} 

Szablon pozwala na elastyczny i bezodrzutowy kod, który jest znacznie bardziej czytelny, a dedykowany delegat zapewnia wydajność.

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});

4

Wolę użyć pojedynczego wystąpienia metody Deleguj zamiast tworzyć nowe wystąpienie za każdym razem. W moim przypadku wyświetlałem komunikaty o postępach i (informacje / błędy) z Backroundworker kopiującego i rzutującego duże dane z instancji SQL. Za każdym razem po około 70000 postępach i połączeniach z wiadomościami mój formularz przestał działać i wyświetlał nowe wiadomości. Nie zdarzyło się to, gdy zacząłem używać jednego delegata globalnej instancji.

delegate void ShowMessageCallback(string message);

private void Form1_Load(object sender, EventArgs e)
{
    ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}

private void ShowMessage(string message)
{
    if (this.InvokeRequired)
        this.Invoke(showMessageDelegate, message);
    else
        labelMessage.Text = message;           
}

void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
    ShowMessage(e.Message);
}

3

Stosowanie:

control.InvokeIfRequired(c => c.Visible = false);

return control.InvokeIfRequired(c => {
    c.Visible = value

    return c.Visible;
});

Kod:

using System;
using System.ComponentModel;

namespace Extensions
{
    public static class SynchronizeInvokeExtensions
    {
        public static void InvokeIfRequired<T>(this T obj, Action<T> action)
            where T : ISynchronizeInvoke
        {
            if (obj.InvokeRequired)
            {
                obj.Invoke(action, new object[] { obj });
            }
            else
            {
                action(obj);
            }
        }

        public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func) 
            where TIn : ISynchronizeInvoke
        {
            return obj.InvokeRequired
                ? (TOut)obj.Invoke(func, new object[] { obj })
                : func(obj);
        }
    }
}

2

Lubię to robić trochę inaczej, lubię nazywać siebie w razie potrzeby Akcją,

    private void AddRowToListView(ScannerRow row, bool suspend)
    {
        if (IsFormClosing)
            return;

        if (this.InvokeRequired)
        {
            var A = new Action(() => AddRowToListView(row, suspend));
            this.Invoke(A);
            return;
        }
         //as of here the Code is thread-safe

jest to przydatny wzorzec, IsFormClosing to pole, które ustawiłem na True, kiedy zamykam formularz, ponieważ wciąż mogą być uruchomione wątki w tle ...


-3

Nigdy nie powinieneś pisać kodu, który wygląda tak:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Jeśli masz taki kod, to aplikacja nie jest bezpieczna dla wątków. Oznacza to, że masz kod, który już wywołuje DoGUISwitch () z innego wątku. Jest za późno, aby sprawdzić, czy jest w innym wątku. InvokeRequire musi zostać wywołane PRZED nawiązaniem połączenia z DoGUISwitch. Nie powinieneś uzyskiwać dostępu do żadnej metody lub właściwości z innego wątku.

Odwołanie: Control.InvokeRequired Właściwość, w której można przeczytać następujące informacje:

Oprócz właściwości InvokeRequired istnieją cztery metody kontrolki, które można bezpiecznie wywołać w wątku: Invoke, BeginInvoke, EndInvoke i CreateGraphics, jeśli uchwyt formantu został już utworzony.

W architekturze jednoprocesorowej nie ma problemu, ale w architekturze wieloprocesorowej możesz przypisać część wątku interfejsu użytkownika procesorowi, w którym działał kod wywołujący ... a jeśli procesor ten różni się od miejsca, w którym wątek interfejsu użytkownika działało wtedy, gdy wątek wywołujący kończy się, system Windows pomyśli, że wątek interfejsu użytkownika został zakończony i zabije proces aplikacji, tzn. aplikacja zakończy działanie bez błędu.


Cześć, dzięki za odpowiedź. Minęły lata, odkąd zadałem to pytanie (i prawie tak długo, odkąd pracowałem z C #), ale zastanawiałem się, czy mógłbyś wyjaśnić coś więcej? Dokumenty, które podlinkowałeś, odnoszą się do konkretnego niebezpieczeństwa wywołania invoke()et al, zanim kontrola otrzyma kontrolę, ale IMHO nie opisuje tego, co opisałeś. Cały sens tego invoke()nonsensu polega na aktualizacji interfejsu użytkownika w sposób bezpieczny dla wątków, i pomyślałbym, że umieszczenie większej liczby instrukcji w kontekście blokowania doprowadziłoby do jąkania się? (Ugh ... cieszę się, że przestałem używać technologii M $. Tak skomplikowane!)
Tom Corelis

Chcę również zauważyć, że pomimo częstego używania oryginalnego kodu (
dawno

3
Wątpię, aby ta odpowiedź była dokładna, ponieważ MSDN pokazuje wiele przykładów takich jak podane przez OP.
publiczny bezprzewodowy
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.