Как избежать утечки дескрипторов при вызове в пользовательском интерфейсе из System.Threading.Timer?

StackOverflow https://stackoverflow.com/questions/1603123

Вопрос

Похоже, что вызов 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.

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

Интересно - это не ответ, но на основании комментария Андрея, я бы подумал, что это не будет обрабатывать утечки таким же образом, однако это будет обрабатывать утечки с той же скоростью, что и упомянутый 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);
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top