2010-06-15 16 views
6

W jakich okolicznościach aktualizacja formantu interfejsu użytkownika z wątku innego niż UI może spowodować ciągłe zwiększenie uchwytów procesów podczas używania delegata i .InvokeRequired?UI Wątek .Invoke() powodujący wyciek rączki?

Na przykład:

public delegate void DelegateUIUpdate(); 
private void UIUpdate() 
{ 
    if (someControl.InvokeRequired) 
    { 
     someControl.Invoke(new DelegateUIUpdate(UIUpdate)); 
     return; 
    } 
    // do something with someControl 
} 

Kiedy to się nazywa w pętli lub w odstępach czasowych, uchwyty dla programu stale wzrastać.

EDIT:

Jeśli powyższe jest wypowiedziało się i wprowadza jako takie:

public delegate void DelegateUIUpdate(); 
private void UIUpdate() 
{ 
    //if (someControl.InvokeRequired) 
    //{ 
    // someControl.Invoke(new DelegateUIUpdate(UIUpdate)); 
    // return; 
    //} 
    CheckForIllegalCrossThreadCalls = false; 
    // do something with someControl 
} 

... wtedy uchwyty zatrzymać inkrementację, jednak nie chcę, aby umożliwić krzyż wątek wywołań, oczywiście.

EDIT 2:

Oto przykład, który pokazuje uchwyty zwiększają:

Thread thread; 
private delegate void UpdateGUI(); 
bool UpdateTheGui = false; 

public Form1() 
{ 
    InitializeComponent(); 

    thread = new Thread(new ThreadStart(MyThreadLoop)); 
    thread.Start(); 
} 

private void MyThreadLoop() 
{ 
    while (true) 
    { 
     Thread.Sleep(500); 
     if (UpdateTheGui) 
     { 
      UpdateTheGui = false; 
      UpdateTheGuiNow(); 
     } 
    } 
} 

private void UpdateTheGuiNow() 
{ 
    if (label1.InvokeRequired) 
    { 
     label1.Invoke(new UpdateGUI(UpdateTheGuiNow)); 
     return; 
    } 

    label1.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss"); 
    label2.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss"); 
    label3.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss"); 
} 

private void btnInvoke_Click(object sender, EventArgs e) 
{ 
    UpdateTheGui = true; 
} 
+0

Mam ten sam problem tutaj, z dokładnie tym samym wywołaniem zegara. Dziękuję za wzmiankę o CheckForIllegalCrossThreadCalls, ponieważ nigdy wcześniej o tym nie słyszałem. – muusbolla

Odpowiedz

3

Metoda Control.Invoke() nie pobiera żadnych uchwytów. Jednak ten kod jest wyraźnie wywoływany z wątku. Wątek ma zużywać uchwyty, 5 z nich.

Klasa Thread nie ma metody Dispose(), chociaż powinna ona istnieć. To było prawdopodobnie z projektu, byłoby bardzo trudno wywołać niezawodnie, niemożliwie tak dla wątków ThreadPoin. 5 uchwytów wymaganych przez wątek jest zwalnianych przez finalizator. Twój program będzie wymagał coraz większej ilości uchwytów, jeśli finalizator nigdy nie będzie działał.

Brak uruchamiania finalizatora jest dość nietypowy. Trzeba by mieć program, który uruchamia wiele wątków, ale nie przydziela dużej ilości pamięci. To zdarza się tylko w testach statycznych. Możesz zdiagnozować ten stan za pomocą Perfmon.exe, użyć liczników wydajności pamięci .NET i sprawdzić, czy kolekcje gen # 0 są wykonywane.

Jeśli zdarzy się to w programie produkcyjnym, musisz osobiście zadzwonić do GC.Collect(), aby uniknąć wycieku uchwytu.

+0

Użyłem opisanego wzorca do aktualizacji składników interfejsu użytkownika w przeszłości, bez problemu zwiększania liczby uchwytów, ale w bieżącym projekcie z pewnością jest coś do zrobienia z tym wywołaniem wątku do interfejsu użytkownika. Jeśli skomentuję fragment, który sprawdza i wywołuje delegata, a zamiast tego 'CheckForIllegalCrossThreadCalls = false;' wtedy uchwyty przestają się zwiększać, chociaż nie chcę tego zostawić w takim stanie. Zaktualizuję pytanie za pomocą tych informacji. – JYelton

+0

Cóż, to niełatwe wyjaśnienie. Daj nam pojęcie o tym, co robi reszta kodu i na jakim konkretnym stwierdzeniu rośnie liczba uchwytów. –

+0

Dodano próbkę, która przedstawia problem bardziej szczegółowo. Przycisk na formularzu uruchamia aktualizację. – JYelton

0

Jest to standardowy wzór za korzystanie Invoke aktualizacje Marshall do wątku UI.

Czy jesteś pewien, że Twój problem nie jest spowodowany przez inny kod w Twojej aplikacji, którego nie ma w Twoim pytaniu?

+0

Nie jestem do końca pewien, ale zobacz komentarz do pytania, a także komentarz do odpowiedzi Hansa. To bardzo nietypowy problem. – JYelton

0

Nie sądzę, że jest to powiązane. Być może tylko czekając, aż garbage collector wyrzuci nowo przydzielony obiekt (y) wewnątrz Invoke().

+0

Wywołanie 'GC.Collect()' faktycznie spowoduje zatrzymanie uchwytów. Co ciekawe, jeśli "GC.Collect()" zostanie dodany po pewnym czasie działania programu, w czasie przerwy nie zmniejszy uchwytów do stanu początkowego, ale po prostu uniemożliwi ich gromadzenie. – JYelton

0

Rzeczywiście widzę ten sam problem z JYeltonem. Mam takie samo wywołanie z poziomu wątku, aby zaktualizować interfejs użytkownika.

Po wywołaniu linii someControl.Invoke(new DelegateUIUpdate(UIUpdate)); uchwyt zwiększa się o jeden. Z pewnością jest jakiś przeciek, ale nie mam pojęcia, co go powoduje. Zostało to zweryfikowane w kilku systemach.

2

Widziałem to samo w moim kodzie.Naprawiłem to, zastępując Invoke przez BeginInvoke. Przeciek uchwytu zniknął.

Doron.

3

miałem ten sam problem z

this.Invoke(new DelegateClockUpdate(ChangeClock), sender, e); 

tworząc jeden uchwyt każdego połączenia.

Uchwyt jest zwiększany, ponieważ Invoke jest synchroniczny i skutecznie uchwyt został zawieszony.

Do przetwarzania wyniku lub asynchronicznej metody BeginInvoke należy użyć przycisku oczekiwania, jak pokazano poniżej.

this.BeginInvoke(new DelegateClockUpdate(ChangeClock), sender, e);  
+0

Dzięki, koleś!Twoja odpowiedź jest taka fajna, że ​​zapisałem moją aplikację z OutOfMemory i 3 dni śledztwa. Prawdopodobnie znasz somw profilujące ninjustsu .... Jesteś prawdziwym drancem! –

0

Wywołanie Aync z wyraźnym zakończeniem rączki. Exapmle:

public static class ActionExtensions 
    { 
    private static readonly ILog log = LogManager.GetLogger(typeof(ActionExtensions)); 

    /// <summary> 
    /// Async exec action. 
    /// </summary> 
    /// <param name="action">Action.</param> 
    public static void AsyncInvokeHandlers(
     this Action action) 
    { 
     if (action == null) 
     { 
     return; 
     } 

     foreach (Action handler in action.GetInvocationList()) 
     { 
     // Initiate the asychronous call. Include an AsyncCallback 
     // delegate representing the callback method, and the data 
     // needed to call EndInvoke. 
     handler.BeginInvoke(
      ar => 
      { 
      try 
      { 
       // Retrieve the delegate. 
       var handlerToFinalize = (Action)ar.AsyncState; 
       // Call EndInvoke to free resources. 
       handlerToFinalize.EndInvoke(ar); 

       var handle = ar.AsyncWaitHandle; 
       if (handle.SafeWaitHandle != null && !handle.SafeWaitHandle.IsInvalid && !handle.SafeWaitHandle.IsClosed) 
       { 
       ((IDisposable)handle).Dispose(); 
       } 
      } 
      catch (Exception exception) 
      { 
       log.Error("Async Action exec error.", exception); 
      } 
      }, 
      handler); 
     } 
    } 
    } 

Zobacz http://msdn.microsoft.com/en-us/library/system.iasyncresult.asyncwaithandle.aspx UWAGA:

Podczas korzystania z metody BeginInvoke delegata wywołać metodę asynchronicznie i uzyskać uchwyt oczekiwania od uzyskanego IAsyncResult, zalecamy, aby zamknąć czekać obsłużyć zaraz po zakończeniu korzystania z niego, wywołując metodę WaitHandle.Close. Jeśli po prostu zwolnisz wszystkie odniesienia do uchwytu oczekiwania, zasoby systemowe zostaną zwolnione, gdy odśmiecanie odzyska uchwyt oczekiwania, ale funkcja czyszczenia śmieci działa bardziej wydajnie, gdy obiekty jednorazowego użytku są jawnie zamknięte lub usuwane. Aby uzyskać więcej informacji, zobacz właściwość AsyncResult.AsyncWaitHandle.

0

Oto metoda rozszerzenie który działa podobnie do normalnej rozmowy Invoke, ale oczyścić uchwyt po:

namespace ExtensionMethods 
{ 
    public static class ExtensionMethods 
    { 
     public static void InvokeAndClose(this Control self, MethodInvoker func) 
     { 
      IAsyncResult result = self.BeginInvoke(func); 
      self.EndInvoke(result); 
      result.AsyncWaitHandle.Close(); 
     } 
    } 
} 

Następnie można nazwać bardzo podobnie do normalnego invoke:

myForm.InvokeAndClose((MethodInvoker)delegate 
{ 
    someControl.Text = "New Value"; 
}); 

Spowoduje to zablokowanie i oczekiwanie na wykonanie polecenia, a następnie zamknięcie klamki przed powrotem.