Почему графические операции в фоновом потоке блокируют графические операции в основном потоке пользовательского интерфейса? - PullRequest
2 голосов
/ 15 декабря 2010

У меня есть фоновый поток, который создает миниатюры изображений в данной папке в градациях серого. Проблема, которую я вижу, заключается в том, что вызов Graphics.DrawImage () в фоновом потоке, похоже, как-то блокирует графические операции в основном потоке пользовательского интерфейса.

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

Я пытался придумать как можно меньший случай репро. Если вы замените форму в проекте по умолчанию на приведенную ниже форму (и у вас будет несколько изображений в папке для тестирования), вы заметите, что анимирующая метка заикается, когда она отскакивает назад и вперед через окно. Тем не менее, если вы раскомментируете #define сверху, чтобы дочерний элемент управления анимировал, а не перерисовывал содержимое окна, он работает идеально гладко.

Кто-нибудь может увидеть, что я здесь делаю неправильно, или помочь мне понять, как избежать этого заикания во время цикла обновления?

//#define USE_LABEL_CONTROL

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using Timer = System.Windows.Forms.Timer;

namespace ThreadTest
{
    public partial class Form1 : Form
    {
        private const string ImageFolder = "c:\\pics";
        private const string ImageType = "*.jpg";

        public Form1()
        {
            InitializeComponent();
        }

        protected override void OnLoad(EventArgs e)
        {
            this.Size = new Size(300, 300);

            string[] ImageFiles = Directory.GetFiles(ImageFolder, 
                                                        ImageType, 
                                                        SearchOption.AllDirectories);

            // kick off a thread to create grayscale thumbnails of all images
            this.thumbnailThread = new Thread(this.thumbnailThreadFunc);
            this.thumbnailThread.Priority = ThreadPriority.Lowest;
            this.thumbnailThread.Start(ImageFiles);

            // set a timer to start us off...
            this.startTimer = new Timer();
            this.startTimer.Interval = 500;
            this.startTimer.Tick += this.startTimer_Tick;
            this.startTimer.Start();

#if USE_LABEL_CONTROL
            this.label.Location = this.labelRect.Location;
            this.label.Size = this.labelRect.Size;
            this.label.Text = "Loaded: 0";
            this.label.BorderStyle = BorderStyle.FixedSingle;
            this.Controls.Add(this.label);
#endif

            base.OnLoad(e);
        }

        void startTimer_Tick(object sender, EventArgs e)
        {
            // kill the timer
            this.startTimer.Stop();

            // update ourself in a loop
            while (this.IsHandleCreated)
            {
                int NextTick = Environment.TickCount + 50;

                // update the label position
                this.labelRect.Offset(this.currentLabelDirection, 0);
                if (this.labelRect.Right == this.ClientRectangle.Right ||
                    this.labelRect.Left == 0)
                {
                    this.currentLabelDirection = -this.currentLabelDirection;
                }

                // update the display
#if USE_LABEL_CONTROL
                this.label.Text = "Loaded: " + this.thumbs.Count;
                this.label.Location = this.labelRect.Location;
#else
                using (Graphics Dest = this.CreateGraphics())
                {
                    this.redrawControl(Dest, this.ClientRectangle);
                }
#endif

                Application.DoEvents();
                Thread.Sleep(Math.Max(0, NextTick - Environment.TickCount));
            }
        }

        private void thumbnailThreadFunc(object ThreadData)
        {
            string[] ImageFiles = (string[]) ThreadData;
            foreach (string ImageFile in ImageFiles)
            {
                if (!this.IsHandleCreated)
                {
                    return;
                }

                using (Image SrcImg = Image.FromFile(ImageFile))
                {
                    Rectangle SrcRect = new Rectangle(Point.Empty, SrcImg.Size);

                    Rectangle DstRect = new Rectangle(Point.Empty, new Size(300, 200));
                    Bitmap DstImg = new Bitmap(DstRect.Width, DstRect.Height);
                    using (Graphics Dst = Graphics.FromImage(DstImg))
                    {
                        using (ImageAttributes Attrib = new ImageAttributes())
                        {
                            Attrib.SetColorMatrix(this.grayScaleMatrix);
                            Dst.DrawImage(SrcImg, 
                                            DstRect, 
                                            0, 0, SrcRect.Width, SrcRect.Height, 
                                            GraphicsUnit.Pixel, 
                                            Attrib);
                        }
                    }

                    lock (this.thumbs)
                    {
                        this.thumbs.Add(DstImg);
                    }
                }
            }
        }

#if !USE_LABEL_CONTROL
        private void redrawControl (Graphics Dest, Rectangle UpdateRect)
        {
            Bitmap OffscreenImg = new Bitmap(this.ClientRectangle.Width, 
                                                this.ClientRectangle.Height);
            using (Graphics Offscreen = Graphics.FromImage(OffscreenImg))
            {
                Offscreen.FillRectangle(Brushes.White, this.ClientRectangle);
                Offscreen.DrawRectangle(Pens.Black, this.labelRect);
                Offscreen.DrawString("Loaded: " + this.thumbs.Count,
                                        SystemFonts.MenuFont,
                                        Brushes.Black,
                                        this.labelRect);
            }
            Dest.DrawImageUnscaled(OffscreenImg, 0, 0);
            OffscreenImg.Dispose();
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            return;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            this.redrawControl(e.Graphics, e.ClipRectangle);
        }
#endif


        private ColorMatrix grayScaleMatrix = new ColorMatrix(new float[][] 
                                                        {
                                                            new float[] {.3f, .3f, .3f, 0, 0},
                                                            new float[] {.59f, .59f, .59f, 0, 0},
                                                            new float[] {.11f, .11f, .11f, 0, 0},
                                                            new float[] {0, 0, 0, 1, 0},
                                                            new float[] {0, 0, 0, 0, 1}
                                                        });
        private Thread thumbnailThread;
        private Timer startTimer;
        private List<Bitmap> thumbs = new List<Bitmap>();
        private Label label = new Label();
        private int currentLabelDirection = 1;
        private Rectangle labelRect = new Rectangle(0, 125, 75, 20);
    }
}

Ответы [ 2 ]

5 голосов
/ 15 декабря 2010

Оказывается, что ответом является использование нескольких процессов для обработки фоновых задач GDI +.Если вы запустите приведенный выше код под профилировщиком параллелизма в VS2010, вы увидите, что поток переднего плана блокируется в критической секции, защищенной вызовом DrawImage () в фоновом потоке.

Этот поток также обсуждает эту проблему иуказывает на то, что, поскольку он использует критическую секцию, блокировки будут для каждого процесса, и фоновые задачи могут распараллеливаться с использованием нескольких процессов вместо потоков:

Распараллеливание GDI + Изменение размера изображения .net

1 голос
/ 15 декабря 2010

Каждый раз, когда у вас возникают проблемы с производительностью, вам необходимо учитывать как можно больше факторов. В вашем случае есть довольно большая разница между реализациями label и redraw.

  • Версия метки перерисовывает только маленькие (или две маленькие) части экрана.
  • Перерисованная версия выполняет гораздо больше работы и перерисовывает большую область.

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

...