Поток пользовательского интерфейса.Invoke () вызывает утечку дескриптора?

в каких обстоятельствах обновление элемента управления UI из потока, отличного от UI, может привести к постоянному увеличению дескрипторов процессов при использовании делегата и .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
}

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

EDIT 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 ответов


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

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

не получить финализатор для запуска довольно необычно. У вас должна быть программа, которая запускает много потоков, но не выделяет много памяти. Обычно это происходит только в статических тестах. Вы можете диагностировать это состояние с помощью Perfmon.exe, используйте счетчики производительности памяти .NET и проверьте, выполняются ли коллекции gen #0.

Если это происходит в производственной программе, вам придется позвонить СБОРЩИК МУСОРА.Соберите () себя, чтобы избежать утечки ручки runaway.


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

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

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

дескриптор увеличивается, потому что Invoke является синхронным и эффективно дескриптор был оставлен висящим.

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

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

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

Дорон.


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

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


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


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

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


вызов Aync с явным дескриптором finalize. 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.


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

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

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