Как избежать утечки дескрипторов при вызове в пользовательском интерфейсе из System.Threading.Timer? - PullRequest
8 голосов
/ 21 октября 2009

Кажется, что вызов Invoke для элемента управления winforms в обратном вызове из System.Threading.Timer утечки обрабатывает до тех пор, пока таймер не будет уничтожен. У кого-нибудь есть идеи как обойти это? Мне нужно опрашивать значение каждую секунду и соответственно обновлять пользовательский интерфейс.

Я попробовал это в тестовом проекте, чтобы убедиться, что это действительно было причиной утечки, а это просто следующее:

    System.Threading.Timer timer;
    public Form1()
    {
        InitializeComponent();
        timer = new System.Threading.Timer(new System.Threading.TimerCallback(DoStuff), null, 0, 500);
    }
    void DoStuff(object o)
    {
        this.Invoke(new Action(() => this.Text = "hello world"));
    }

Это будет пропускать 2 ручки / секунду, если вы смотрите в диспетчере задач Windows.

Ответы [ 4 ]

6 голосов
/ 22 октября 2009

Invoke действует как пара BeginInvoke / EndInvoke в том смысле, что он отправляет сообщение в поток пользовательского интерфейса, создает дескриптор и ожидает на этом дескрипторе, чтобы определить, когда завершен вызываемый метод. Именно эта ручка "протекает". Вы можете видеть, что это безымянные события, использующие Process Explorer для отслеживания дескрипторов во время работы приложения.

Если бы IASyncResult был IDisposable, утилизация объекта позаботилась бы о очистке ручки. Поскольку это не так, дескрипторы очищаются, когда сборщик мусора запускается и вызывает финализатор объекта IASyncResult. В этом можно убедиться, добавив GC.Collect () после каждых 20 вызовов DoStuff - число дескрипторов падает каждые 20 секунд. Конечно, «решение» проблемы путем добавления вызовов в GC.Collect () является неправильным способом решения проблемы; пусть сборщик мусора сделает свое дело.

Если вам не нужно , чтобы вызов Invoke был синхронным, используйте BeginInvoke вместо Invoke и не вызывайте EndInvoke; конечный результат будет делать то же самое, но никакие дескрипторы не будут созданы или «пропущены».

2 голосов
/ 22 октября 2009

Хорошо, я уделил этому немного больше времени и похоже, что на самом деле он не протекает ручками, это просто неопределенная природа сборщика мусора. Я поднял его до 10 мс за такт, и он очень быстро взлетел, а через 30 секунд снова упал.

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

2 голосов
/ 21 октября 2009

Есть ли причина, по которой вы не можете использовать System.Windows.Forms.Timer здесь? Если таймер привязан к этой форме, вам даже не нужно вызывать.

0 голосов
/ 22 октября 2009

Интересно - это не ответ, но на основании комментария Андрея, я бы подумал, что это не будет обрабатывать утечки так же, как это делает утечки с той же скоростью, что и упомянутый OP.

System.Threading.Timer timer;
    public Form2()
    {
        InitializeComponent();

    }

    private void UpdateFormTextCallback()
    {
        this.Text = "Hello World!";
    }

    private Action UpdateFormText;

    private void DoStuff(object value)
    {
        this.Invoke(UpdateFormText);
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        timer = new System.Threading.Timer(new TimerCallback(DoStuff), null, 0, 500);
        UpdateFormText = new Action(UpdateFormTextCallback);
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...