Утечки памяти в .NET CF при запуске форм в отдельных потоках - PullRequest
0 голосов
/ 17 июня 2009

РЕДАКТИРОВАТЬ - обнулять нить перед измерением чистовой памяти

Это все работает .NET Compact Framework 2.0 под Windows CE 5.0.

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

До сих пор мой подход заключался в создании нового потока и его наличии. A) Создайте форму и непрерывно вызывайте Application.DoEvents, пока она не закроется, или b) Создайте форму, передайте ее в Application.Run (Форма).

Ниже приведен пример формы, иллюстрирующей проблему.

public partial class TestForm : Form
{
    public TestForm()
    {
        InitializeComponent();
    }

    private void DoMemoryTest(bool useApplicationRun)
    {
        GC.WaitForPendingFinalizers();
        GC.Collect();
        long originalMem = GC.GetTotalMemory(true);

        Thread t;
        if (useApplicationRun)
            t = new Thread(new ThreadStart(AppRunThread));
        else
            t = new Thread(new ThreadStart(DoEventThread));
        t.Start();

        Thread.Sleep(3000);//Dodgey hack
        t.Join();
        t = null;

        GC.WaitForPendingFinalizers();
        GC.Collect();
        long terminatingMem = GC.GetTotalMemory(true);

        MessageBox.Show(String.Format("An increase of {0} bytes was measured from {1} bytes", 
                        terminatingMem - originalMem, originalMem));
    }

    private void button1_Click(object sender, EventArgs e)
    {
        DoMemoryTest(false);
    }

    private void button2_Click(object sender, EventArgs e)
    {
        DoMemoryTest(true);
    }

    private void AppRunThread()
    {
        Application.Run(new OpenCloseForm());
    }

    private void DoEventThread()
    {
        using (OpenCloseForm frm = new OpenCloseForm())
        {
            frm.Show();
            do
            {
                Application.DoEvents();
            } while (frm.Showing);
        }
    }

    /// <summary>
    /// Basic form that opens for a short period before shutting itself
    /// </summary>
    class OpenCloseForm : Form
    {
        public OpenCloseForm()
        {
            this.Text = "Closing Soon";
            this.Size = new Size(100, 100);
            this.TopMost = true;
        }

        public volatile bool Showing = false; //dodgy hack for DoEventThread

        System.Threading.Timer timer;
        protected override void OnLoad(EventArgs e)
        {
            Showing = true;
            base.OnLoad(e);
            timer = new System.Threading.Timer(new TimerCallback(TimerTick), null, 1000, 1000);
        }

        delegate void CloseDelegate();
        private void TimerTick(object obj)
        {
            this.Invoke(new CloseDelegate(this.Close));
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
            Showing = false;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (timer != null)
                {
                    timer.Dispose();
                    timer = null;
                }
            }
            base.Dispose(disposing);
        }
    }

    //Designer code to follow....

    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.timer1 = new System.Windows.Forms.Timer();
        this.button1 = new System.Windows.Forms.Button();
        this.button2 = new System.Windows.Forms.Button();
        this.SuspendLayout();
        // 
        // button1
        // 
        this.button1.Location = new System.Drawing.Point(32, 47);
        this.button1.Name = "button1";
        this.button1.Size = new System.Drawing.Size(116, 39);
        this.button1.TabIndex = 1;
        this.button1.Text = "DoEvents Loop";
        this.button1.Click += new System.EventHandler(this.button1_Click);
        // 
        // button2
        // 
        this.button2.Location = new System.Drawing.Point(32, 115);
        this.button2.Name = "button2";
        this.button2.Size = new System.Drawing.Size(116, 39);
        this.button2.TabIndex = 2;
        this.button2.Text = "Application.Run";
        this.button2.Click += new System.EventHandler(this.button2_Click);
        // 
        // TestForm
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
        this.AutoScroll = true;
        this.ClientSize = new System.Drawing.Size(177, 180);
        this.Controls.Add(this.button2);
        this.Controls.Add(this.button1);
        this.Name = "TestForm";
        this.Text = "TestForm";
        this.TopMost = true;
        this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.Timer timer1;
    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.Button button2;
}

Я что-то упустил в отношении утилизации средств управления? У кого-нибудь есть идеи, куда идти?

Заранее спасибо.

Ответы [ 3 ]

4 голосов
/ 17 июня 2009

Хорошо, первое, что я должен сказать об этом коде, это WTF ?! Вам нужно немного изучить, как работают сообщения Windows. Я немного расскажу здесь, но вам действительно нужно понять это, прежде чем погрузиться в игру и попытаться сделать сумасшедшие вещи, подобные тому, что я вижу здесь.

  1. Когда создается окно, все «действия» для этого окна доставляются через сообщения Windows. Когда вы звоните «Обновить», «Кликаете» или что-то в этом роде, это сообщение Windows.
  2. Эти сообщения отправляются из «насоса сообщений», который представляет собой просто цикл, который вызывает API-интерфейсы PeekMessage, GetMEsssage, TranslateMessage и DispatchMessage.
  3. Application.Run делает это в управляемом коде.
  4. Сообщения Windows для формы должны доставляться в том же контексте потока, что и окно, которое было создано. Это означает, что насос также должен быть на одной и той же резьбе. Вот почему существует Control.Invoke.
  5. Насос в одном потоке не будет даже видеть сообщения в окне в другом потоке

Так что, безусловно, есть некоторые проблемы с тем, что вы делаете. Я не уверен, что представляет собой «общая картина» того, чего вы пытаетесь достичь, и как вы могли заметить эту ошибку, но этот код говорит мне, что у вас есть некоторые фундаментальные проблемы в вашей архитектуре.

Но как насчет этой утечки, вы думаете, вы все равно нашли? Ну, нет утечки. Вы не понимаете CF (и управляемое) управление памятью. Опять же, исследование рекомендуется, но MSDN имеет действительно хорошую веб-трансляцию, которая хорошо освещает ее .

Суть этого сценария в том, что вы создали несколько объектов в отдельных потоках. Эти потоки создают кучу вещей, некоторые из которых IDisposable, некоторые нет. Когда нить срывается, эти предметы больше не имеют корней, поэтому доступны для сбора. Когда вызывается Collect, GC обходит все корни и отмечает каждый объект, имеющий корень (метку). Те, которые этого не делают, «освобождаются» (развертки). Область в куче GC, в которой они находились, просто больше не помечена как используемая - подробности см. В веб-трансляции. Если элемент реализовал IDisposabe, то получает новый корень , поэтому финализатор все еще может существовать и запускать в следующем цикле GC . И, наконец, завершает поток финализатора (недетерминированно).

Ваш код не учитывает это поведение. Вы не запускали Collect дважды. Вы не ожидали финализаторы после вызова Collect (и простого вызова WaitForPendingFinalizers может быть недостаточно). Поскольку сами ваши потоки не помечены как фоновые потоки, кто знает, какова их позиция в их жизненном цикле и состояние использования GC.

Итак, когда мы действительно приступаем к этому, возникает вопрос: что именно вы пытаетесь решить? Вы работаете в среде управляемой памяти. Если вы не видите OOM, вы почти всегда не должны беспокоиться об уровнях памяти - в этом весь смысл наличия GC. Не пытайтесь строить замысловатые учебные занятия Уолдо и попросите нас найти утечку.

Если у вас действительно есть проблема, то сначала убедитесь, что дизайн вашего приложения соответствует тому, как должны быть написаны приложения для Windows, а затем используйте инструменты, такие как RPM , чтобы определить, какие корни хранятся памяти и исправления созданных вами утечек (да, утечки все еще могут происходить и происходят в управляемом коде). Конечно, вы всегда можете задать разумные вопросы о проблеме real world и здесь.

EDIT

Microsoft, похоже, удалила содержимое веб-трансляции, о котором я упоминал выше. Надеюсь, они смогут найти его и опубликовать повторно, но в то же время (и если они его никогда не найдут) у меня по крайней мере есть PowerPoint, который я использовал для первоначального выступления в MEDC, и он доступен в моем блоге .

0 голосов
/ 17 июня 2009

Во-первых, я хотел бы спросить, почему вы создаете достаточно форм в других потоках, что вас волнует 392 байта, но это не так, как я думаю.

Первое, с чего я бы начал, это экземпляр управляемого потока, от которого вы не избавляетесь в методе DoMemoryTest. Вызовите t.Join () после вызова Thread.Sleep (3000), затем установите для него значение null (чтобы его можно было GCed даже в режиме отладки).

0 голосов
/ 17 июня 2009

Я могу ошибаться, так как я не эксперт по CF, но .net не освободит необходимую память, если система не находится под давлением.

Насколько я понимаю, если приложению потребуется один раз по x байтов памяти, в какой-то момент ему, вероятно, понадобится еще как минимум x байтов. Он не освободит эту память, если это не нужно для чего-то еще в ОС.

...