gpt4 book ai didi

c# - 为什么后台线程中的图形操作会阻塞主 UI 线程中的图形操作?

转载 作者:行者123 更新时间:2023-12-02 11:20:58 25 4
gpt4 key购买 nike

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

我可能会误解我在这里看到的内容,并且直到今晚晚些时候才有机会进行任何深入的分析,尽管我不希望能够找到太多东西。

我试图想出尽可能小的复制案例。如果您将默认项目中的表单替换为下面的表单(并在文件夹中有一些图像进行测试),您会注意到动画标签在窗口中来回弹跳时会出现卡顿。然而,如果您取消顶部#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);
}
}

最佳答案

事实证明,答案是使用多个进程来处理后台 GDI+ 任务。如果您在 VS2010 中的并发分析器下运行上述代码,您将看到前台线程阻塞在由后台线程中的 DrawImage() 调用保护的关键部分上。

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

Parallelizing GDI+ Image Resizing .net

关于c# - 为什么后台线程中的图形操作会阻塞主 UI 线程中的图形操作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4445990/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com