У меня есть фоновый поток, который создает миниатюры изображений в данной папке в градациях серого. Проблема, которую я вижу, заключается в том, что вызов 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);
}
}