GDI + Проблема с очередью рисования

товарищи) Я обнаружил интересное поведение метода Invalidate в многопоточных приложениях. Я надеюсь, что вы могли бы помочь мне с проблемой ...

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

Вот пример: у меня есть форма (MysticForm) с двумя панелями (SlowRenderPanel) на ней. Каждая панель имеет таймер и с периодом 50 мс вызывается метод Invalidate (). В методе OnPaint я рисую номер текущего вызова OnPaint в центре панели. Но обратите внимание, что в OnPaint метод System.Threading.Thread.Sleep (50) вызывается для имитации процедуры рисования в течение длительного времени.

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

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1 {
    static class Program {
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MysticForm());
        }
    }

    public class MysticForm : Form {
        public SlowRenderPanel panel1;
        public SlowRenderPanel panel2;

        public MysticForm() {
            // add 2 panels to the form
            Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Left, BackColor = Color.Red, Width = ClientRectangle.Width / 2 });
            Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Right, BackColor = Color.Blue, Width = ClientRectangle.Width / 2 });
        }
    }

    public class SlowRenderPanel : Panel {
        // synchronized timer
        private System.Windows.Forms.Timer timerSafe = null;
        // simple timer
        private System.Threading.Timer timerUnsafe = null;
        // OnPaint call counter
        private int counter = 0;

        // allows to use one of the above timers
        bool useUnsafeTimer = true;

        protected override void Dispose(bool disposing) {
            // active timer disposal
            (useUnsafeTimer ? timerUnsafe as IDisposable : timerSafe as IDisposable).Dispose();
            base.Dispose(disposing);
        }

        public SlowRenderPanel() {
            // anti-blink
            DoubleBuffered = true;
            // large font
            Font = new Font(Font.FontFamily, 36);

            if (useUnsafeTimer) {
                // simple timer. starts in a second. calls Invalidate() with period = 50ms
                timerUnsafe = new System.Threading.Timer(state => { Invalidate(); }, null, 1000, 50);
            } else {
                // safe timer. calls Invalidate() with period = 50ms
                timerSafe = new System.Windows.Forms.Timer() { Interval = 50, Enabled = true };
                timerSafe.Tick += (sender, e) => { Invalidate(); };
            }
        }

        protected override void OnPaint(PaintEventArgs e) {
            string text = counter++.ToString();

            // simulate large bitmap drawing
            System.Threading.Thread.Sleep(50);

            SizeF size = e.Graphics.MeasureString(text, Font);
            e.Graphics.DrawString(text, Font, Brushes.Black, new PointF(Width / 2f - size.Width / 2f, Height / 2f - size.Height / 2f));
            base.OnPaint(e);
        }

    }

}

Отладка info:

1) На каждой панели есть поле bool useUnsafeTime (по умолчанию установлено в true), которое позволяет использовать System.Windows.Forms.Timer (false) вместо System.Threading.Timer (true). В первом случае (System.Windows.Forms.Timer) все работает отлично. Удаление вызова System.Threading.Sleep в OnPaint также обеспечивает хорошее выполнение.

2) Установка интервала таймера на 25 мс или меньше предотвращает перекрашивание второй панели вообще (пока пользователь не изменяет размер формы).

3) Использование System.Windows.Forms.Timer приводит к увеличению скорости

4 ) Принудительное управление для входа в контекст синхронизации (Invoke) не имеет смысла. Я имею в виду, что Invalidate (invalidateChildren = false) является «потоко-безопасным» и может иметь различное поведение в различных контекстах

. 5) Ничего интересного не найдено в сравнении IL этих двух таймеров ... Они просто используют разные функции WinAPI для установки и удалите таймеры (AddTimerNative, DeleteTimerNative для Threading.Timer; SetTimer, KillTimer для Windows.Forms.Timer), а Windows.Forms.Timer использует NativeWindow ' Marshal.Copy (buffer, 0, unmanagedPointer, buffer.Length); SomeCommandThatCanThrowAnException (); ...

У меня есть следующий блок кода:

IntPtr unmanagedPointer = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, unmanagedPointer, buffer.Length);
SomeCommandThatCanThrowAnException();
Marshal.FreeHGlobal(unmanagedPointer);

Если блок будет обернут в попытку, и команда FreeHGlobal будет помещена в блок finally. (В случае, если средняя команда выдает исключение).

Кажется, имеет смысл, что в этом случае, наконец, удалось бы предотвратить утечки памяти, однако из примеров, которые я нашел в Интернете, наконец, не используется. Возможно, ресурсы в любом случае автоматически удаляются (даже если они неуправляемые).

6
задан vicsz 19 August 2010 в 20:29
поделиться

2 ответа

Неуправляемая память, выделенная с помощью Marshal.AllocHGlobal, не освобождается автоматически.

Таким образом, размещение Marshal.FreeHGlobal в блоке finally действительно является хорошей идеей:

IntPtr unmanagedPointer = Marshal.AllocHGlobal(buffer.Length);
try
{
    Marshal.Copy(buffer, 0, unmanagedPointer, buffer.Length);
    SomeCommandThatCanThrowAnException();
}
finally
{
    Marshal.FreeHGlobal(unmanagedPointer);
}

В найденных вами примерах, вероятно, для краткости опущена обработка ошибок.


Если вы выделяете неуправляемую память для долгосрочных целей (то есть не освобождаете ее в рамках одного метода), вам может быть интересно заключить указатель в объект, производный от SafeHandle (например, SafeBuffer ).

SafeHandle реализует шаблон IDisposable, поэтому неуправляемая память будет освобождена, когда вы удаляете объект или когда сборщик мусора собирает объект. SafeHandle также является производным от класса CriticalFinalizerObject, что означает, что он получит специальную обработку со стороны среды CLR, чтобы убедиться, что память действительно освобождена.

class HGlobal : SafeBuffer
{
    public HGlobal(int cb)
        : base(true)
    {
        this.SetHandle(Marshal.AllocHGlobal(cb));
        this.Initialize((ulong)cb);
    }

    protected override bool ReleaseHandle()
    {
        Marshal.FreeHGlobal(this.handle);
        return true;
    }
}

Пример:

using (var h = new HGlobal(buffer.Length))
{
    h.WriteArray(0, buffer, 0, buffer.Length);
}

Примечание. SafeBuffer - настоящий зверь, поэтому рекомендуется проявлять осторожность.

Примечание 2: SafeHandles хорошо работают с P / Invoke и полностью исключают необходимость передачи IntPtrs.

SafeBuffers предназначены для безопасного управления неуправляемой памятью из C #, поэтому в зависимости от того, что вы делаете (выделение неуправляемой памяти для использования с P / Invoke или управление неуправляемой памятью из C #), вам следует выбрать SafeHandle или SafeBuffer в качестве базового класса соответствующим образом.

13
ответ дан 8 December 2019 в 15:58
поделиться

Совершенно верно. Он никогда никогда не освобождается автоматически, это неуправляемая память.

2
ответ дан 8 December 2019 в 15:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: