UI Thread .Invoke () вызывает утечку дескриптора? - PullRequest
7 голосов
/ 15 июня 2010

При каких обстоятельствах обновление элемента управления пользовательского интерфейса из потока, не являющегося пользовательским интерфейсом, может приводить к постоянному увеличению хендлов процессов при использовании делегата и .InvokeRequired?

Например:

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

Когда это вызывается в цикле или с интервалами таймера, дескрипторы программы постоянно увеличиваются.

EDIT:

Если вышеизложенное закомментировано и изменено следующим образом:

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

... затем дескрипторы останавливаются с приращением, однако я, конечно, не хочу разрешать вызовы между потоками.

РЕДАКТИРОВАТЬ 2:

Вот пример, показывающий увеличение ручки:

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;
}

Ответы [ 8 ]

4 голосов
/ 22 октября 2011

У меня была такая же проблема с

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

созданием одного дескриптора для каждого вызова.

Дескриптор увеличивается, потому что Invoke является синхронным и фактически дескриптор оставлен зависшим.

Для обработки результата следует использовать либо дескриптор ожидания, либо метод асинхронного BeginInvoke, как показано ниже.

this.BeginInvoke(new DelegateClockUpdate(ChangeClock), sender, e);    
3 голосов
/ 03 августа 2011

Я видел то же самое в моем коде. Я исправил это, заменив Invoke на BeginInvoke. Утечка в ручке исчезла.

Дорон.

3 голосов
/ 16 июня 2010

Метод Control.Invoke () не использует никаких дескрипторов.Однако этот код явно вызывается из потока.Thread потребляет дескрипторов, 5 из них.

Класс Thread не имеет метода Dispose (), хотя он должен иметь один.Вероятно, это было задумано, было бы очень трудно назвать надежным, а это невозможно для потоков пула потоков.5 дескрипторов, которые требуются потоку, освобождаются финализатором.Ваша программа будет требовать все большего количества дескрипторов, если финализатор никогда не запускается.

Не запускать финализатор довольно необычно.Вы должны иметь программу, которая запускает много потоков, но не выделяет много памяти.Это происходит только в статических тестах.Вы можете диагностировать это состояние с помощью Perfmon.exe, использовать счетчики производительности памяти .NET и проверить, выполняются ли коллекции поколения №.

Если это происходит в производственной программе, вам придется вызывать GC.Соберите () себя, чтобы избежать утечки из бегущей ручки.

0 голосов
/ 21 июля 2015

Вот метод расширения, который функционирует аналогично обычному вызову Invoke, но очистит дескриптор после:

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();
        }
    }
}

Затем вы можете вызвать его очень похоже на обычный вызов:

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

Он заблокирует и дождется выполнения делегата, затем закроет дескриптор перед возвратом.

0 голосов
/ 03 апреля 2014

Aync вызов с явным дескриптором финализации. 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);
      }
    }
  }

См. http://msdn.microsoft.com/en-us/library/system.iasyncresult.asyncwaithandle.aspx Примечание:

Когда вы используете метод BeginInvoke делегата для асинхронного вызова метода и получения дескриптора ожидания из полученного IAsyncResult, мы рекомендуем закрыть дескриптор ожидания, как только вы закончите его использовать, вызвав WaitHandle.Close метод. Если вы просто освобождаете все ссылки на дескриптор ожидания, системные ресурсы освобождаются, когда сборщик мусора возвращает дескриптор ожидания, но сборка мусора работает более эффективно, когда одноразовые объекты явно закрываются или удаляются. Для получения дополнительной информации см. Свойство AsyncResult.AsyncWaitHandle.

0 голосов
/ 16 июня 2010

Я действительно вижу ту же проблему, что и Джелтон. У меня такой же вызов из потока для обновления пользовательского интерфейса.

Как только вызывается строка someControl.Invoke(new DelegateUIUpdate(UIUpdate));, дескриптор увеличивается на единицу. Конечно, на вызове есть какая-то утечка, но я понятия не имею, что ее вызывает. Это было проверено на нескольких системах.

0 голосов
/ 15 июня 2010

Я не думаю, что это связано. Возможно, просто ожидаем, что сборщик мусора удалит недавно выделенные объекты внутри Invoke ().

0 голосов
/ 15 июня 2010

Это стандартный шаблон для использования Invoke для маршалловых обновлений в потоке пользовательского интерфейса.

Вы уверены, что ваша проблема не вызвана другим кодом в вашем приложении, который не включен в ваш вопрос?

...