为什么图形操作在后台线程块图形操作的主UI线程? [英] Why do Graphics operations in background thread block Graphics operations in main UI thread?

查看:115
本文介绍了为什么图形操作在后台线程块图形操作的主UI线程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经得到了在给定文件夹中创建图像的灰度缩略图的后台线程。我看到的问题是,在后台线程Graphics.DrawImage()调用,似乎在主UI线程上以某种方式阻止图形操作。



我可能是误解我所看到这里,不会有机会做任何深入的剖析,直到今晚稍后,虽然我不指望能找到很多。



我试图想出尽可能小的重现bug的例子越好。如果您在下面的表格默认替换项目的形式(并有一个文件夹中的部分图片来测试),你会注意到动画标签将结巴,因为它来回反射在窗口。然而,如果你在上面注释中的#define使孩子控制动画,而不是重新绘制窗口内容,它运行完美流畅。



任何人都可以看到我是什么做错了,或者帮助我弄清楚如何避免更新循环在这个口吃?

  //#定义USE_LABEL_CONTROL 

按系统;
使用System.Collections.Generic;
使用System.Drawing中;
使用System.Drawing.Imaging;
:使用System.IO;
使用的System.Threading;使用System.Windows.Forms的
;使用定时器= System.Windows.Forms.Timer
;

命名空间ThreadTest
{
公共部分Form1类:表格
{
私人常量字符串ImageFolder =C:\\pics
私人常量字符串=将ImageType* .JPG;

公共Form1中()
{
的InitializeComponent();
}

保护覆盖无效的OnLoad(EventArgs的发送)
{
this.Size =新的大小(300,300);

的String []的图像文件= Directory.GetFiles(ImageFolder,
ImageType和
SearchOption.AllDirectories);

//揭开序幕线程创建的所有图像
this.thumbnailThread =新主题(this.thumbnailThreadFunc)的灰度缩略图;
this.thumbnailThread.Priority = ThreadPriority.Lowest;
this.thumbnailThread.Start(图像文件中);

//设置一个计时器开始我们送行......
this.startTimer =新的Timer();
this.startTimer.Interval = 500;
this.startTimer.Tick + = this.startTimer_Tick;
this.startTimer.Start();

#如果USE_LABEL_CONTROL
this.label.Location = this.labelRect.Location;
this.label.Size = this.labelRect.Size;
this.label.Text =装:0;
this.label.BorderStyle = BorderStyle.FixedSingle;
this.Controls.Add(this.label);
#ENDIF

base.OnLoad(E);
}

无效startTimer_Tick(对象发件人,EventArgs五)
{
//终止计时器
this.startTimer.Stop();

//更新我们自己在一个循环
,而(this.IsHandleCreated)
{
INT NextTick = Environment.TickCount + 50;

//更新标签位置
this.labelRect.Offset(this.currentLabelDirection,0);
如果(this.labelRect.Right == || this.ClientRectangle.Right
this.labelRect.Left == 0)
{
this.currentLabelDirection = -this.currentLabelDirection ;
}

//更新显示
#如果USE_LABEL_CONTROL
this.label.Text =加载:+ this.thumbs.Count;
this.label.Location = this.labelRect.Location;使用
的#else
(图形目的地= this.CreateGraphics())
{
this.redrawControl(目的地,this.ClientRectangle);
}
#ENDIF

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

私人无效thumbnailThreadFunc(对象ThreadData)
{
的String []的图像文件=(字符串[])ThreadData;
的foreach(以图像文件中的字符串镜像文件)
{
如果
{
回报(this.IsHandleCreated!);
}

用(图SrcImg = Image.FromFile(镜像文件))
{
矩形SrcRect =新的Rectangle(Point.Empty,SrcImg.Size);

矩形DstRect =新的Rectangle(Point.Empty,新的大小(300,200));
位图DstImg =新位图(DstRect.Width,DstRect.Height);使用
(图形Dst的= Graphics.FromImage(DstImg))
{使用(ImageAttributes ATTRIB =新ImageAttributes())

{
Attrib.SetColorMatrix(这一点。 grayScaleMatrix);
Dst.DrawImage(SrcImg,
DstRect,
0,0,SrcRect.Width,SrcRect.Height,
GraphicsUnit.Pixel,
ATTRIB);
}
}

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

#如果!USE_LABEL_CONTROL
私人无效redrawControl(图形目的地,矩形UpdateRect)
{
位图OffscreenImg =新位图(this.ClientRectangle.Width,
this.ClientRectangle.Height);
使用(图形画外= Graphics.FromImage(OffscreenImg))
{
Offscreen.FillRectangle(Brushes.White,this.ClientRectangle);
Offscreen.DrawRectangle(Pens.Black,this.labelRect);
Offscreen.DrawString(加载:+ this.thumbs.Count,
SystemFonts.MenuFont,
Brushes.Black,
this.labelRect);
}
Dest.DrawImageUnscaled(OffscreenImg,0,0);
OffscreenImg.Dispose();
}

保护覆盖无效OnPaintBackground(PaintEventArgs的E)
{
的回报;
}

保护覆盖无效的OnPaint(PaintEventArgs的E)
{
this.redrawControl(e.Graphics,e.ClipRectangle);
}
#ENDIF


私人嘉洛斯grayScaleMatrix =新嘉洛斯(新浮法[] []
{
新的浮动[] { .3f,.3f,.3f,0,0},
新的浮动[] {.59f,.59f,.59f,0,0},
新的浮动[] {.11f。部11f,.11f,0,0},
新的浮动[] {0,0,0,1,0},
新的浮动[] {0,0,0,0,1}
});
私人螺纹thumbnailThread;
私人定时器startTimer所;
私有列表<&位图GT;拇指=新的List<&位图GT;();
自有品牌标签=新的Label();
私人INT currentLabelDirection = 1;
私人矩形labelRect =新的Rectangle(0,125,75,20);
}
}


解决方案

据事实证明,答案是使用多个进程来处理后台GDI +的任务。如果您在VS2010并发分析器根据上面的代码,你会看到在前台线程阻塞在后台线程的DrawImage()调用获得了关键部分。



这线程还讨论了这个问题,并指出,由于它是用一个关键部分,锁将每个进程和后台任务可以使用多个进程,而不是线程并行化:



并行化GDI +图像大小调整.NET


I've got a background thread that is creating grayscale thumbnails of images in a given folder. The problem I'm seeing is that the Graphics.DrawImage() call in the background thread seems to be somehow blocking Graphics operations on the main UI thread.

I may be misinterpreting what I'm seeing here, and won't have a chance to do any in-depth profiling until later tonight, though I don't expect to be able to find much.

I've tried to come up with as small a repro case as possible. If you replace the form in a default project with the form below (and have some images in a folder to test with), you'll notice that the animating label will stutter as it bounces back and forth across the window. Yet if you uncomment the #define at the top so that a child control animates rather than redrawing the window contents, it runs perfectly smoothly.

Can anyone see what I'm doing wrong here or help me figure out how to avoid this stutter during the update loop?

//#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);
    }
}

解决方案

It turns out that the answer is to use multiple processes to handle background GDI+ tasks. If you run the above code under the concurrency profiler in VS2010, you'll see the foreground thread blocking on a critical section secured by the DrawImage() call in the background thread.

This thread also discusses this issue and points out that since it's using a critical section, the locks will be per-process and the background tasks can be parallelized using multiple processes instead of threads:

Parallelizing GDI+ Image Resizing .net

这篇关于为什么图形操作在后台线程块图形操作的主UI线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆