Unikasz problemów związanych z Invoke/BeginInvoke w obsłudze zdarzeń WinForm w wielu wątkach?

Wciąż nękają mnie wątki w tle w interfejsie WinForm. Dlaczego? Oto niektóre z problemów:

  1. oczywiście najważniejsza sprawa, nie mogę modyfikować kontrolki, chyba że wykonuję na tym samym wątku, który ją stworzył.
  2. Jak wiesz, Invoke, BeginInvoke, itd. są dostępne dopiero po utworzeniu kontrolki.
  3. nawet po Requieresinvoke zwraca true, BeginInvoke nadal może rzucać obiekt i nawet jeśli nie rzuci, może nigdy nie wykonać kodu, jeśli kontrola jest niszczona.
  4. nawet po Requiesinvoke zwraca true, wywołanie może w nieskończoność zawieszać oczekiwanie na wykonanie przez kontrolkę, która została usunięta w tym samym czasie co wywołanie wywołania.

Szukam eleganckiego rozwiązania tego problemu, ale zanim przejdę do szczegółów tego, czego szukam, pomyślałem, że wyjaśnię problem. Ma to na celu wzięcie ogólnego problemu i postawienie za nim bardziej konkretnego przykładu. Dla tego przykładu powiedzmy, że przenosimy większe ilości danych przez internet. Interfejs użytkownika musi być w stanie wyświetlić okno dialogowe postępu dla już trwającego transferu. Okno dialogowe postępu powinno być stale i szybko aktualizowane (aktualizuje się od 5 do 20 razy na sekundę). Użytkownik może zamknąć okno dialogowe postępu w dowolnym momencie i przywołać je ponownie w razie potrzeby. I dalej, udawajmy dla argumentów, że jeśli okno dialogowe jest widoczne, musi przetwarzać każde zdarzenie postępu. Użytkownik może kliknąć Anuluj w oknie dialogowym postępu i poprzez modyfikując args zdarzenia, anuluj operację.

Teraz potrzebuję rozwiązania, które zmieści się w następującym polu ograniczeń:

  1. pozwala wątkowi roboczemu na wywołanie metody na kontrolerze / formularzu i blokowanie / czekanie do zakończenia wykonania.
  2. pozwól, aby samo okno dialogowe wywołało tę samą metodę przy inicjalizacji lub tym podobne (a więc nie używaj invoke).
  3. nie obciąża implementacji metody obsługi ani wywołującego zdarzenia, rozwiązanie powinno jedynie zmienić Zdarzenie sam abonament.
  4. odpowiednio obsłuż blokowanie wywołań do okna dialogowego, które może być w trakcie usuwania. Niestety nie jest to tak proste, jak sprawdzenie IsDisposed.
  5. musi być używany z dowolnym typem zdarzenia (Załóżmy delegata typu EventHandler)
  6. nie może tłumaczyć wyjątków od TargetInvocationException.
  7. rozwiązanie musi działać z. Net 2.0 i wyższymi

Czy można to rozwiązać, biorąc pod uwagę powyższe ograniczenia? Szukałem i przekopałem się przez niezliczone blogi i dyskusje i niestety wciąż jestem z pustymi rękami.

Aktualizacja: zdaję sobie sprawę, że to pytanie nie ma łatwej odpowiedzi. Jestem na tej stronie dopiero od kilku dni i widziałem ludzi z dużym doświadczeniem odpowiadających na pytania. Mam nadzieję, że jedna z tych osób rozwiązała to na tyle, że nie spędzę tygodnia lub tyle czasu, aby zbudować rozsądne rozwiązanie.

Update # 2: Ok, postaram się opisać problem w trochę więcej szczegółów i zobacz, co (jeśli w ogóle) się trzęsie. Poniższe właściwości, które pozwalają nam określić jego stan, budzą obawy...

  1. Kontrola.InvokeRequired = Documented zwraca false, jeśli jest uruchomiony w bieżącym wątku lub jeśli IsHandleCreated zwraca false dla wszystkich rodziców. Niepokoi mnie implementacja InvokeRequired, która może albo rzucić ObjectDisposedException, albo potencjalnie nawet odtworzyć uchwyt obiektu. I od InvokeRequired może zwracać true, gdy nie jesteśmy w stanie wywołać (Dispose in progress) i może zwracać false, nawet jeśli będziemy musieli użyć invoke (Create in progress), to po prostu nie można ufać we wszystkich przypadkach. Jedyny przypadek, w którym możemy zaufać invokerequired zwracając false, to gdy IsHandleCreated zwraca true zarówno przed, jak i po wywołaniu (BTW dokumenty MSDN dla InvokeRequired wspominają o sprawdzaniu IsHandleCreated).

  2. Kontrola.IsHandleCreated = zwraca true jeśli uchwyt został przypisany do kontrolki; w przeciwnym razie false. Chociaż IsHandleCreated jest bezpiecznym wywołaniem, może ulec awarii, jeśli kontrola jest w trakcie odtwarzania jej uchwytu. Ten potencjalny problem wydaje się być rozwiązywalny poprzez wykonanie blokady(sterowania) podczas uzyskiwania dostępu do IsHandleCreated i InvokeRequired.

  3. Kontrola.Disposing = zwraca true, jeśli kontrola jest w trakcie usuwania.

  4. Kontrola.IsDisposed = zwraca true, jeśli kontrolka została usunięta. I ' m biorąc pod uwagę zapisanie się do zdarzenia zbycia i sprawdzanie właściwości IsDisposed w celu określenia, czy BeginInvoke kiedykolwiek się zakończy. Dużym problemem jest tutaj brak blokady synchronizacji podczas przejścia Disposing - > Disposed. Jest możliwe, że jeśli subskrybujesz Zdarzenie usuwane i po tym zweryfikujesz, że disposed = = false & & IsDisposed = = false, możesz nigdy nie zobaczyć zdarzenia usuwanego. Wynika to z faktu, że implementacja Dispose sets Disposing = false, a następnie ustawia Disposed = true. Zapewnia to możliwość odczytu zarówno usuwania, jak i wyświetlania jako false na kontrolce usuwania.

... Głowa mnie boli : (mam nadzieję, że powyższe informacje rzucą nieco więcej światła na problemy dla każdego, kto ma te problemy. Doceniam Twoje wolne cykle myślowe.

Zbliżamy się do kłopotów... Poniżej znajduje się późniejsza połowa kontroli.Metoda DestroyHandle ():
if (!this.RecreatingHandle && (this.threadCallbackList != null))
{
    lock (this.threadCallbackList)
    {
        Exception exception = new ObjectDisposedException(base.GetType().Name);
        while (this.threadCallbackList.Count > 0)
        {
            ThreadMethodEntry entry = (ThreadMethodEntry) this.threadCallbackList.Dequeue();
            entry.exception = exception;
            entry.Complete();
        }
    }
}
if ((0x40 & ((int) ((long) UnsafeNativeMethods.GetWindowLong(new HandleRef(this.window, this.InternalHandle), -20)))) != 0)
{
    UnsafeNativeMethods.DefMDIChildProc(this.InternalHandle, 0x10, IntPtr.Zero, IntPtr.Zero);
}
else
{
    this.window.DestroyHandle();
}

Zauważysz ObjectDisposedException jest wysyłany do wszystkich oczekujących wywołań między wątkami. Wkrótce po tym jest wezwanie do tego.okno.DestroyHandle (), która z kolei niszczy okno i ustawia jego odniesienie do obsługi IntPtr.Zero w ten sposób uniemożliwia dalsze wywołania metody BeginInvoke (a dokładniej MarshaledInvoke, która obsługuje zarówno BeginInvoke, jak i Invoke). Problem polega na tym, że po zwolnieniu blokady na threadCallbackList można wstawić nowy wpis przed wątkiem kontrolki zeruje klamkę okna. Wydaje mi się, że jest to przypadek, który widzę, choć rzadko, na tyle często, aby zatrzymać zwolnienie.

Update # 4:

Przepraszam, że ciągnę to dalej; jednak uznałem, że warto to udokumentować. Udało mi się rozwiązać większość powyższych problemów i zawężam rozwiązanie, które działa. Trafiłem na jeszcze jeden numer, O który się martwiłem, ale do tej pory nie widziałem "in-the-wild".

Ten problem ma związek z geniuszem, który napisał "Control".Uchwyt własność:
    public IntPtr get_Handle()
    {
        if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired)
        {
            throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name }));
        }
        if (!this.IsHandleCreated)
        {
            this.CreateHandle();
        }
        return this.HandleInternal;
    }

To samo w sobie nie jest takie złe (niezależnie od moich opinii na temat modyfikacji get {}), jednak w połączeniu z właściwością InvokeRequired lub metodą Invoke / BeginInvoke jest złe. Oto podstawowy przepływ wywołania:

if( !this.IsHandleCreated )
    throw;
... do more stuff
PostMessage( this.Handle, ... );

Problem polega na tym, że z innego wątku mogę pomyślnie przejść przez pierwszą instrukcję if, po której uchwyt jest niszczony przez wątek kontrolki, powodując w ten sposób, że właściwość Get of the Handle ponownie utworzy okno zajmij się moją nicią. Może to spowodować, że wyjątek zostanie podniesiony w oryginalnym wątku kontrolki. Ten naprawdę mnie zaskoczył, ponieważ nie ma sposobu, aby się przed tym uchronić. Gdyby używali tylko własności wewnętrznej i testowali na wynik IntPtr.Zero to nie będzie problemem.

Author: csharptest.net, 2009-09-01

13 answers

Twój scenariusz, zgodnie z opisem, idealnie pasuje BackgroundWorker - dlaczego po prostu tego nie wykorzystać? Twoje wymagania dotyczące rozwiązania są zbyt ogólne i raczej nieuzasadnione - wątpię, że istnieje jakiekolwiek rozwiązanie, które zaspokoiłoby je wszystkie.

 22
Author: Pavel Minaev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-01 19:11:01

Wpadłem na ten problem jakiś czas temu i wymyśliłem rozwiązanie obejmujące konteksty synchronizacji. Rozwiązaniem jest dodanie metody rozszerzenia do SynchronizationContext, która wiąże konkretnego delegata do wątku, z którym jest związany SynchronizationContext. Wygeneruje on nowego delegata, który po wywołaniu wywoła wywołanie do wątku appropraite, a następnie wywoła oryginalnego delegata. To sprawia, że prawie niemożliwe dla konsumentów delegata nazwać go w niewłaściwym kontekst.

Post na blogu w temacie:

 8
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-09 06:52:00

OK, kilka dni później skończyłem tworzyć rozwiązanie. Rozwiązuje wszystkie wymienione ograniczenia i cele w początkowym poście. Użycie jest proste i proste:

myWorker.SomeEvent += new EventHandlerForControl<EventArgs>(this, myWorker_SomeEvent).EventHandler;

Gdy wątek roboczy wywoła to zdarzenie, obsłuży wymagane wywołanie do wątku sterującego. Zapewnia, że nie zawiesi się w nieskończoność i będzie konsekwentnie rzucać ObjectDisposedException, jeśli nie jest w stanie wykonać w wątku kontrolnym. Stworzyłem inne derywacje klasy, jeden aby zignorować błąd, a drugi bezpośrednio wywołać delegata, jeśli kontrola nie jest dostępna. Wydaje się działać dobrze i w pełni przechodzi kilka testów, które odtwarzają powyższe problemy. Jest tylko jeden problem z rozwiązaniem nie mogę zapobiec bez naruszenia ograniczenia #3 powyżej. Ten problem jest ostatnim (Update # 4) w opisie problemu, problemy z wątkiem w uchwycie get. Może to spowodować nieoczekiwane zachowanie w oryginalnym wątku kontrolnym i regularnie widziałem InvalidOperationException () wyrzucone podczas wywołania Dispose (), ponieważ uchwyt w procesie tworzenia w moim wątku. Aby umożliwić sobie z tym radzenie, zapewniam blokadę dostępu do funkcji, które będą korzystać z kontrolki.Zajmij się nieruchomością. Pozwala to na przeciążenie metody DestroyHandle i zablokowanie jej przed wywołaniem implementacji bazowej. Jeśli tak się stanie, Ta klasa powinna być całkowicie bezpieczna dla wątków (z tego co wiem).

public class Form : System.Windows.Forms.Form
{
    protected override void DestroyHandle()
    {
        lock (this) base.DestroyHandle();
    }
}

Możesz zauważyć podstawowy aspekt rozwiązywanie martwego zamka stało się pętlą wyborczą. Początkowo z powodzeniem rozwiązałem przypadki testowe, obsługując Zdarzenie kontrolki dla usuniętych i obsługiwanych oraz używając wielu uchwytów oczekiwania. Po dokładniejszym przejrzeniu okazało się, że subskrypcja/wypisanie z tych wydarzeń nie jest bezpieczne dla wątków. Dlatego zdecydowałem się przepytać IsHandleCreated, aby nie tworzyć niepotrzebnych sporów na temat zdarzeń wątku i tym samym uniknąć możliwości dalszego wytwarzania martwego zamka stan.

W każdym razie, oto rozwiązanie, które wymyśliłem:

/// <summary>
/// Provies a wrapper type around event handlers for a control that are safe to be
/// used from events on another thread.  If the control is not valid at the time the
/// delegate is called an exception of type ObjectDisposedExcpetion will be raised.
/// </summary>
[System.Diagnostics.DebuggerNonUserCode]
public class EventHandlerForControl<TEventArgs> where TEventArgs : EventArgs
{
    /// <summary> The control who's thread we will use for the invoke </summary>
    protected readonly Control _control;
    /// <summary> The delegate to invoke on the control </summary>
    protected readonly EventHandler<TEventArgs> _delegate;

    /// <summary>
    /// Constructs an EventHandler for the specified method on the given control instance.
    /// </summary>
    public EventHandlerForControl(Control control, EventHandler<TEventArgs> handler)
    {
        if (control == null) throw new ArgumentNullException("control");
        _control = control.TopLevelControl;
        if (handler == null) throw new ArgumentNullException("handler");
        _delegate = handler;
    }

    /// <summary>
    /// Constructs an EventHandler for the specified delegate converting it to the expected
    /// EventHandler&lt;TEventArgs> delegate type.
    /// </summary>
    public EventHandlerForControl(Control control, Delegate handler)
    {
        if (control == null) throw new ArgumentNullException("control");
        _control = control.TopLevelControl;
        if (handler == null) throw new ArgumentNullException("handler");

        //_delegate = handler.Convert<EventHandler<TEventArgs>>();
        _delegate = handler as EventHandler<TEventArgs>;
        if (_delegate == null)
        {
            foreach (Delegate d in handler.GetInvocationList())
            {
                _delegate = (EventHandler<TEventArgs>) Delegate.Combine(_delegate,
                    Delegate.CreateDelegate(typeof(EventHandler<TEventArgs>), d.Target, d.Method, true)
                );
            }
        }
        if (_delegate == null) throw new ArgumentNullException("_delegate");
    }


    /// <summary>
    /// Used to handle the condition that a control's handle is not currently available.  This
    /// can either be before construction or after being disposed.
    /// </summary>
    protected virtual void OnControlDisposed(object sender, TEventArgs args)
    {
        throw new ObjectDisposedException(_control.GetType().Name);
    }

    /// <summary>
    /// This object will allow an implicit cast to the EventHandler&lt;T> type for easier use.
    /// </summary>
    public static implicit operator EventHandler<TEventArgs>(EventHandlerForControl<TEventArgs> instance)
    { return instance.EventHandler; }

    /// <summary>
    /// Handles the 'magic' of safely invoking the delegate on the control without producing
    /// a dead-lock.
    /// </summary>
    public void EventHandler(object sender, TEventArgs args)
    {
        bool requiresInvoke = false, hasHandle = false;
        try
        {
            lock (_control) // locked to avoid conflicts with RecreateHandle and DestroyHandle
            {
                if (true == (hasHandle = _control.IsHandleCreated))
                {
                    requiresInvoke = _control.InvokeRequired;
                    // must remain true for InvokeRequired to be dependable
                    hasHandle &= _control.IsHandleCreated;
                }
            }
        }
        catch (ObjectDisposedException)
        {
            requiresInvoke = hasHandle = false;
        }

        if (!requiresInvoke && hasHandle) // control is from the current thread
        {
            _delegate(sender, args);
            return;
        }
        else if (hasHandle) // control invoke *might* work
        {
            MethodInvokerImpl invocation = new MethodInvokerImpl(_delegate, sender, args);
            IAsyncResult result = null;
            try
            {
                lock (_control)// locked to avoid conflicts with RecreateHandle and DestroyHandle
                    result = _control.BeginInvoke(invocation.Invoker);
            }
            catch (InvalidOperationException)
            { }

            try
            {
                if (result != null)
                {
                    WaitHandle handle = result.AsyncWaitHandle;
                    TimeSpan interval = TimeSpan.FromSeconds(1);
                    bool complete = false;

                    while (!complete && (invocation.MethodRunning || _control.IsHandleCreated))
                    {
                        if (invocation.MethodRunning)
                            complete = handle.WaitOne();//no need to continue polling once running
                        else
                            complete = handle.WaitOne(interval);
                    }

                    if (complete)
                    {
                        _control.EndInvoke(result);
                        return;
                    }
                }
            }
            catch (ObjectDisposedException ode)
            {
                if (ode.ObjectName != _control.GetType().Name)
                    throw;// *likely* from some other source...
            }
        }

        OnControlDisposed(sender, args);
    }

    /// <summary>
    /// The class is used to take advantage of a special-case in the Control.InvokeMarshaledCallbackDo()
    /// implementation that allows us to preserve the exception types that are thrown rather than doing
    /// a delegate.DynamicInvoke();
    /// </summary>
    [System.Diagnostics.DebuggerNonUserCode]
    private class MethodInvokerImpl
    {
        readonly EventHandler<TEventArgs> _handler;
        readonly object _sender;
        readonly TEventArgs _args;
        private bool _received;

        public MethodInvokerImpl(EventHandler<TEventArgs> handler, object sender, TEventArgs args)
        {
            _received = false;
            _handler = handler;
            _sender = sender;
            _args = args;
        }

        public MethodInvoker Invoker { get { return this.Invoke; } }
        private void Invoke() { _received = true; _handler(_sender, _args); }

        public bool MethodRunning { get { return _received; } }
    }
}
Jeśli zobaczysz coś nie tak, daj mi znać.
 7
Author: csharptest.net,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-03 22:34:37

Nie zamierzam pisać wyczerpującego rozwiązania dla Ciebie, które spełnia wszystkie twoje wymagania, ale zaproponuję perspektywę. Ogólnie rzecz biorąc, myślę, że strzelasz do księżyca z tymi wymaganiami.

The Invoke/BeginInvoke Architektura po prostu wykonuje dostarczonego delegata w wątku interfejsu sterownika, wysyłając mu wiadomość Windows, a pętla wiadomości sama wykonuje delegata. Konkretne działania tego nie mają znaczenia, ale chodzi o to, że nie ma szczególnego powodu, aby musisz użyć tej architektury do synchronizacji wątku z wątkiem interfejsu użytkownika. Wszystko, czego potrzebujesz, to jakaś inna pętla uruchomiona, na przykład w Forms.Timer lub coś w tym stylu, która monitoruje {[3] } dla delegatów do wykonania i robi to. Wdrożenie własnego byłoby dość proste, choć Nie wiem, co konkretnie by Dla ciebie dało, czego Invoke i BeginInvoke nie zapewniają.

 2
Author: Adam Robinson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-01 19:15:37

To nie jest tak naprawdę odpowiedź na drugą część pytania, ale dołączę ją tylko dla odniesienia:

private delegate object SafeInvokeCallback(Control control, Delegate method, params object[] parameters);
public static object SafeInvoke(this Control control, Delegate method, params object[] parameters)
{
    if (control == null)
        throw new ArgumentNullException("control");
    if (control.InvokeRequired)
    {
        IAsyncResult result = null;
        try { result = control.BeginInvoke(new SafeInvokeCallback(SafeInvoke), control, method, parameters); }
        catch (InvalidOperationException) { /* This control has not been created or was already (more likely) closed. */ }
        if (result != null)
            return control.EndInvoke(result);
    }
    else
    {
        if (!control.IsDisposed)
            return method.DynamicInvoke(parameters);
    }
    return null;
}

Ten kod powinien unikać najczęstszych pułapek z Invoke/BeginInvoke i jest łatwy w użyciu . Just turn

if (control.InvokeRequired)
    control.Invoke(...)
else
    ...

Do

control.SafeInvoke(...)

Podobna konstrukcja jest możliwa dla BeginInvoke.

 1
Author: Filip Navara,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-01 21:24:37

Długie pytanie. Postaram się uporządkować odpowiedź, żebyś mógł mnie poprawić, jeśli coś źle zrozumiałem, ok?

1) chyba, że masz bardzo dobry powód, aby wywoływać metody interfejsu użytkownika bezpośrednio z różnych wątków, nie. zawsze możesz wybrać model producenta / konsumenta za pomocą programów obsługi zdarzeń:

protected override void OnLoad()
{
    //...
    component.Event += new EventHandler(myHandler);
}

protected override void OnClosing()
{
    //...
    component.Event -= new EventHandler(myHandler);
}

MyHandler będzie uruchamiany za każdym razem, gdy komponent w innym wątku będzie musiał wykonać coś w interfejsie, na przykład. Również konfigurowanie obsługi zdarzenia w OnLoad i anulowanie subskrypcji w OnClosing gwarantuje, że zdarzenia będą odbierane/obsługiwane tylko przez interfejs użytkownika, gdy jego uchwyt jest tworzony i gotowy do przetworzenia zdarzeń. Nie będziesz nawet mógł uruchamiać zdarzeń w tym oknie dialogowym, jeśli jest ono w trakcie usuwania, ponieważ nie będziesz już subskrybowany do zdarzenia. Jeśli inne zdarzenie zostanie wywołane podczas przetwarzania, zostanie ono ustawione w kolejce.

Możesz przekazać wszystkie potrzebne informacje w argumentach zdarzenia: czy aktualizujesz postęp, zamykasz okno itp.

2) nie potrzebujesz InvokeRequired, jeśli używasz modelu, który zaproponowałem powyżej. W tym przykładzie wiesz, że jedyną rzeczą, która odpala myHandler, będzie twój komponent, który żyje na przykład w innym wątku.

private void myHandler(object sender, EventArgs args)
{
    BeginInvoke(Action(myMethod));
}

Więc zawsze możesz użyć invoke, aby upewnić się, że jesteś we właściwym wątku.

3) Uważaj na połączenia synchroniczne. Jeśli chcesz, możesz zastąpić use Invoke zamiast BeginInvoke. Spowoduje to zablokowanie komponentu do czasu wystąpienia zdarzenia przetwarzane. Jeśli jednak w interfejsie musisz komunikować się z czymś, co jest wyłączne dla wątku, w którym mieszka twój komponent, możesz mieć problemy z impasem. (Nie wiem, czy wyraziłem się jasno, proszę dać mi znać). Miałem problemy z wyjątkami przy użyciu reflection (TargetInvocationException) i BeginInvoke( ponieważ rozpoczynają inny wątek, tracisz część śladu stosu), ale nie przypominam sobie, żebym miał duże problemy z wywołaniem wywołań, więc powinieneś być bezpieczny, jeśli chodzi o wyjątki.

Długa odpowiedź. Jeśli przypadkiem przegapiłem którekolwiek z twoich wymagań lub źle zrozumiałem coś, co powiedziałeś (angielski nie jest moim ojczystym językiem, więc nigdy nie jesteśmy pewni), daj mi znać.
 1
Author: diogoriba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-04 18:55:40

Staram się organizować wszystkie takie komunikaty wywoławcze do GUI jako fire and forget(obsługa wyjątku, który GUI może rzucić ze względu na stan rasy przy usuwaniu formularza).

W ten sposób, jeśli nigdy nie wykona żadnej szkody nie jest czynione.

Jeśli GUI musi odpowiedzieć na wątek roboczy, ma sposób na skuteczne odwrócenie powiadomienia. Dla prostych potrzeb BackgroundWorker już to obsługuje.

 1
Author: Chris Chilvers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-08 23:36:13

To dość trudne pytanie. Jak wspomniałem w komentarzu, nie sądzę, że można to rozwiązać, biorąc pod uwagę udokumentowane ograniczenia. Możesz go zhakować, biorąc pod uwagę konkretną implementację. NET framework: znajomość implementacji różnych funkcji Członkowskich może pomóc ci oszukiwać, chwytając blokady tu i tam, i wiedząc, że " to w porządku, aby wywoływać inne funkcje członkowskie w innym wątku."

Moja podstawowa odpowiedź brzmi: "nie."Nienawidzę mówić, że to niemożliwe, bo Bardzo wierzę w. Net framework. Poza tym jestem stosunkowo nowicjuszem, nie studiowałem frameworków w ogóle, czy CS, ale internet jest otwarty(nawet dla ignorantów takich jak ja)!

Na inny temat, argument może być postawiony i dobrze wspierany, " nigdy nie powinieneś potrzebować Invoke, tylko używać BeginInvoke, i odpalić i zapomnieć."Nie będę się starał go poprzeć, ani nawet powiedzieć, że jest to poprawne twierdzenie, ale powiem, że wspólna implementacja jest nieprawidłowa i stanowi pracuje (mam nadzieję) jeden.

Oto wspólna implementacja (zaczerpnięta z innej odpowiedzi tutaj):

protected override void OnLoad()
{
    //...
    component.Event += new EventHandler(myHandler);
}

protected override void OnClosing()
{
    //...
    component.Event -= new EventHandler(myHandler);
}
To nie jest bezpieczne. Komponent mógł łatwo zacząć wywoływać listę wywołania tuż przed wypisaniem i dopiero po zakończeniu usuwania wywoływany jest program obsługi. Chodzi o to, że nie jest udokumentowane, w jaki sposób każdy komponent musi używać mechanizmu zdarzeń w. Net, i szczerze mówiąc, nie musi cię w ogóle wypisywać: Po rozdaniu Twój numer telefonu, nikt nie musi go kasować!

Lepiej jest:

protected override void OnLoad(System.EventArgs e)
{
    component.Event += new System.EventHandler(myHandler);
}    
protected override void OnFormClosing(FormClosedEventArgs e)
{
    component.Event -= new System.EventHandler(myHandler);
    lock (lockobj)
    {
        closing = true;
    }
}
private void Handler(object a, System.EventArgs e)
{
    lock (lockobj)
    {
        if (closing)
            return;
        this.BeginInvoke(new System.Action(HandlerImpl));
    }
}
/*Must be called only on GUI thread*/
private void HandlerImpl()
{
    this.Hide();
}
private readonly object lockobj = new object();
private volatile bool closing = false;
Daj mi znać, jeśli coś przeoczyłem.
 1
Author: Limited Atonement,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-22 18:05:40

Jeśli nie podoba Ci się BackgroundWoker (zgodnie z opisem @ Pavel), możesz zajrzeć do tej biblioteki http://www.wintellect.com/PowerThreading.aspx .

 0
Author: Kane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-01 19:14:42

Jeśli to rozumiem, dlaczego musisz usunąć okno dialogowe postępu, gdy aplikacja jest uruchomiona? Dlaczego po prostu nie pokazać i ukryć go na żądanie użytkowników? Brzmi to tak, jakby twój problem był przynajmniej trochę prostszy.

 0
Author: alexD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-01 19:18:56

Dlaczego po prostu nie ukryć okna dialogowego, gdy użytkownik je odrzuca? To powinno działać dobrze, jeśli nie pokażesz tego okna modalnie. (użyj show zamiast showdialog). Wierzę, że możesz zachować swoje okno postępu na wierzchu swojego okna (jeśli potrzebujesz), przekazując hosta do okna dialogowego podczas wywoływania show.

 0
Author: JMarsch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-04 18:13:14

Używanie {[1] } jest przyjemne przy tworzeniu System.ComponentModel.Component, np. BackgroundWorker. Poniższy fragment kodu przedstawia sposób, w jaki FileSystemWater obsługuje zdarzenia.

    ''' <summary>
    ''' Gets or sets the object used to marshal the event handler calls issued as a result of finding a file in a search.
    ''' </summary>
    <IODescription(SR.FSS_SynchronizingObject), DefaultValue(CType(Nothing, String))> _
    Public Property SynchronizingObject() As System.ComponentModel.ISynchronizeInvoke
        Get
            If (_synchronizingObject Is Nothing) AndAlso (MyBase.DesignMode) Then
                Dim oHost As IDesignerHost = DirectCast(MyBase.GetService(GetType(IDesignerHost)), IDesignerHost)
                If (Not (oHost Is Nothing)) Then
                    Dim oRootComponent As Object = oHost.RootComponent
                    If (Not (oRootComponent Is Nothing)) AndAlso (TypeOf oRootComponent Is ISynchronizeInvoke) Then
                        _synchronizingObject = DirectCast(oRootComponent, ISynchronizeInvoke)
                    End If
                End If
            End If
            Return _synchronizingObject
        End Get
        Set(ByVal Value As System.ComponentModel.ISynchronizeInvoke)
            _synchronizingObject = Value
        End Set
    End Property

    Private _onStartupHandler As EventHandler

    Protected Sub OnStartup(ByVal e As EventArgs)
        If ((Not Me.SynchronizingObject Is Nothing) AndAlso Me.SynchronizingObject.InvokeRequired) Then
            Me.SynchronizingObject.BeginInvoke(_onStartupHandler, New Object() {Me, e})
        Else
            _onStartupHandler.Invoke(Me, e)
        End If
    End Sub
 0
Author: AMissico,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 14:27:39

Oto, czego obecnie używam. Opiera się na wykorzystaniu SynchronizationContext i został zainspirowany artykułem na blogu Jaredpara - zobacz jego odpowiedź powyżej. Może nie jest to idealne rozwiązanie, ale pozwala uniknąć niektórych problemów operacji, których również doświadczałem.

   // Homemade Action-style delegates to provide .Net 2.0 compatibility, since .Net 2.0 does not 
   //  include a non-generic Action delegate nor Action delegates with more than one generic type 
   //  parameter. (The DMethodWithOneParameter<T> definition is not needed, could be Action<T> 
   //  instead, but is defined for consistency.) Some interesting observations can be found here:
   //  http://geekswithblogs.net/BlackRabbitCoder/archive/2011/11/03/c.net-little-wonders-the-generic-action-delegates.aspx
   public delegate void DMethodWithNoParameters();
   public delegate void DMethodWithOneParameter<T>(T parameter1);
   public delegate void DMethodWithTwoParameters<T1, T2>(T1 parameter1, T2 parameter2);
   public delegate void DMethodWithThreeParameters<T1, T2, T3>(T1 parameter1, T2 parameter2, T3 parameter3);


   /// <summary>
   /// Class containing support code to use the SynchronizationContext mechanism to dispatch the 
   /// execution of a method to the WinForms UI thread, from another thread. This can be used as an 
   /// alternative to the Control.BeginInvoke() mechanism which can be problematic under certain 
   /// conditions. See for example the discussion here:
   /// http://stackoverflow.com/questions/1364116/avoiding-the-woes-of-invoke-begininvoke-in-cross-thread-winform-event-handling
   ///
   /// As currently coded this works with methods that take zero, one, two or three arguments, but 
   /// it is a trivial job to extend the code for methods taking more arguments.
   /// </summary>
   public class WinFormsHelper
   {
      // An arbitrary WinForms control associated with thread 1, used to check that thread-switching 
      //  with the SynchronizationContext mechanism should be OK
      private readonly Control _thread1Control = null;

      // SynchronizationContext for the WinForms environment's UI thread
      private readonly WindowsFormsSynchronizationContext _synchronizationContext;


      /// <summary>
      /// Constructor. This must be called on the WinForms UI thread, typically thread 1. (Unless 
      /// running under the Visual Studio debugger, then the thread number is arbitrary.)
      ///
      /// The provided "thread 1 control" must be some WinForms control that will remain in 
      /// existence for as long as this object is going to be used, for example the main Form 
      /// control for the application.
      /// </summary>
      /// <param name="thread1Control">see above</param>
      public WinFormsHelper(Control thread1Control)
      {
         _thread1Control = thread1Control;
         if (thread1Control.InvokeRequired)
            throw new Exception("Not called on thread associated with WinForms controls.");

         _synchronizationContext =
                            SynchronizationContext.Current as WindowsFormsSynchronizationContext;
         if (_synchronizationContext == null) // Should not be possible?
            throw new Exception("SynchronizationContext.Current = null or wrong type.");
      }


      // The following BeginInvoke() methods follow a boilerplate pattern for how these methods 
      // should be implemented - they differ only in the number of arguments that the caller wants 
      // to provide.

      public void BeginInvoke(DMethodWithNoParameters methodWithNoParameters)
      {
         _synchronizationContext.Post((object stateNotUsed) =>
         {
            if (!_thread1Control.IsDisposed)
               methodWithNoParameters();
         }, null);
      }


      public void BeginInvoke<T>(DMethodWithOneParameter<T> methodWithOneParameter, T parameter1)
      {
         _synchronizationContext.Post((object stateNotUsed) =>
         {
            if (!_thread1Control.IsDisposed)
               methodWithOneParameter(parameter1);
         }, null);
      }


      public void BeginInvoke<T1, T2>(DMethodWithTwoParameters<T1, T2> methodWithTwoParameters,
                                      T1 parameter1, T2 parameter2)
      {
         _synchronizationContext.Post((object stateNotUsed) =>
         {
            if (!_thread1Control.IsDisposed)
               methodWithTwoParameters(parameter1, parameter2);
         }, null);
      }


      public void BeginInvoke<T1, T2, T3>(DMethodWithThreeParameters<T1, T2, T3> methodWithThreeParameters,
                                          T1 parameter1, T2 parameter2, T3 parameter3)
      {
         _synchronizationContext.Post((object stateNotUsed) =>
         {
            if (!_thread1Control.IsDisposed)
               methodWithThreeParameters(parameter1, parameter2, parameter3);
         }, null);
      }
   }
 0
Author: RenniePet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-04 11:09:44