Rev*_*Rev 5 delphi multithreading windows-xp image-processing delphi-7
长话短说,我离熟练的程序员很远,事实上,到目前为止,我最复杂的程序是纯ASCII字符串操作,简单数学和数组搜索/排序,无论是在Free Pascal还是以后,Delphi 7和Java.这是几年前,当我在高中教师(普通帕斯卡)学习编程时.后来我继续成为一名程序员(与D7和Java,以及一些C++会面),但由于个人原因,我已经退出了编程研究,从那以后,我没有编写一行代码.
嗯,对不起长时间的介绍,所以...最近我决定重新编程作为我的爱好,主要是因为我没有找到一个合适的程序来完成我想要完成的一些任务.尽管我对显式参数,指针,对象,类,构造函数和线程这些相当基本的东西有了微弱的理解,但是在编程手册的帮助下,Delphi帮助文件和互联网,我设法在Delphi 7中编写了一个简单的程序.可以使用外部库在给定目录中加载和显示某些图像文件格式,从而可以在它们之间任意切换(使用GUI),并在文本文件中记录一些信息(主要用于调试目的).
但是,当我尝试使图像加载并显示函数线程时,我在当前版本的代码中遇到了问题.首先,为了更好地理解,我将解释我的程序的逻辑.
首先,在主窗体的FormCreate事件中,程序在当前目录(exe所在的位置)中查找支持的图像文件.如果没有图像文件,则将当前目录设置为较高级别的目录(使用标准Windows文件系统符号"..")并检查图像.支持的图像文件由文件扩展名决定.无论如何,这个资源管理器函数在动态数组中存储找到的图像文件名和文件类型标识符(这是一个字节).然后,使用此数组作为参考,第一个支持的图像文件由正确的库加载并显示在表单中.GUI具有按钮和组合框以在图像之间切换,每个控件的OnClick或OnSelect(组合框)事件设置变量关于所谓的当前图像文件并调用图像加载器和使用参考阵列的显示器功能.
问题是某些图像太大以至于加载需要很长时间,因此GUI在图像完全加载和显示之前无法响应.我试图通过将每个图像加载器函数初始化为线程来使该程序成为线程.虽然GUI现在响应更快,但该程序肯定存在两个新错误.
第一个是程序在更改图像时有时会随机崩溃,出现的消息要么是指" JPEG错误#58 "(据说在Delphi的内置jpeg库中意味着"无效的文件结构")," EAccessViolation"异常," EOSError"异常(包括" 系统错误,代码5 ")," 未知软件异常 "," 运行时错误216 "以及有关内存位置和失败读取操作的错误消息.在使用线程之前,没有出现这些错误消息,但我当然希望(并且必须)在程序中使用线程.
另一个小错误是,当快速连续点击界面按钮时,似乎所有加载和显示都会发生,尽管是以一种迟滞然后快速的方式.我真的不知道如何"杀死"一个线程并"再次"启动它来加载现在当前的文件而不是它试图加载几百毫秒前的过时文件.
我以下面的方式开始一个线程:
LoaderThread := CreateThread(nil, 0, Addr(LoadPicture), nil, 0, LoaderThreadID);
CloseHandle(LoaderThread);
Run Code Online (Sandbox Code Playgroud)
我在主窗体的FormCreate事件中使用了两次(尽管其中只有一个在任何开始时执行),并且在GUI控件中使用OnClick或OnSelect事件来促进控件的所需功能(例如跳到最后一个图像) .
有什么建议?先感谢您!:)
更新:这是我的一些(好吧,几乎所有)源代码:
procedure TMainForm.FormCreate(Sender: TObject);
begin
MainForm.DoubleBuffered := true;
MainJPEG := TJPEGImage.Create;
MainJPEG.ProgressiveDisplay := true;
MainJPEG.Smoothing := true;
MainJPEG.Performance := jpBestQuality;
MainPNG := TPNGObject.Create;
MainGIF := TGIFImage.Create;
AssignFile(Log, '_NyanLog.txt');
CurrentDir := GetCurrentDir;
ExploreCurrentDir;
if CurrentDirHasImages = false then
begin
SetCurrentDir('..');
CurrentDir := GetCurrentDir;
ExploreCurrentDir;
end;
if CurrentDirHasImages = true then
begin
CurrentFilename := ImagesOfCurrentDir[CurrentPos].Filename;
CurrentFiletype := ImagesOfCurrentDir[CurrentPos].Filetype;
LoaderThread := BeginThread(nil, 0, Addr(LoadImage), nil, 0, LoaderThreadID);
CloseHandle(LoaderThread);
if Length(ImagesOfCurrentDir) > 1 then
begin
MainForm.NextButton.Enabled := true;
MainForm.EndButton.Enabled := true;
MainForm.SlideshowButton.Enabled := true;
MainForm.SlideshowIntervalUpDown.Enabled := true;
end;
UpdateTitleBar;
end
else UpdateTitleBar;
end;
procedure ExploreCurrentDir;
var
Over: boolean;
begin
CurrentPos := 0;
Over := false;
ReWrite(Log);
Write(Log, 'blablabla');
if FindFirst(CurrentDir+'\*.*', faAnyFile-faDirectory, Find) = 0 then
begin
CurrentFilename := Find.Name;
DetermineFiletype;
if CurrentFiletype <> UNSUPPORTED then
begin
SetLength(ImagesOfCurrentDir, CurrentPos+1);
ImagesOfCurrentDir[CurrentPos].Filename := CurrentFilename;
ImagesOfCurrentDir[CurrentPos].Filetype := CurrentFiletype;
MainForm.ImagelistComboBox.AddItem(CurrentFilename, nil);
Write(Log, 'blablabla');
CurrentPos := Succ(CurrentPos);
end;
while Over = false do
begin
if FindNext(Find) = 0 then
begin
CurrentFilename := Find.Name;
DetermineFiletype;
if CurrentFiletype <> UNSUPPORTED then
begin
SetLength(ImagesOfCurrentDir, CurrentPos+1);
ImagesOfCurrentDir[CurrentPos].Filename := CurrentFilename;
ImagesOfCurrentDir[CurrentPos].Filetype := CurrentFiletype;
MainForm.ImagelistComboBox.AddItem(CurrentFilename, nil);
Write(Log, 'blablabla');
CurrentPos := Succ(CurrentPos);
end;
end
else
begin
FindClose(Find);
Over := true;
end;
end;
CurrentDirImageCount := Length(ImagesOfCurrentDir);
CurrentDirHasImages := true;
Write(Log, 'blablabla');
end;
if CurrentDirHasImages = false then Write(Log, 'blablabla');
CloseFile(Log);
CurrentPos := 0;
end;
procedure LoadImage; //procedure #1 which should be used in a thread
begin
if CurrentFiletype = BMP then
begin
MainForm.MainImage.Picture := nil;
MainForm.MainImage.Picture.LoadFromFile(CurrentFilename)
end
else
if CurrentFiletype = JPEG then
begin
MainForm.MainImage.Picture := nil;
MainJPEG.LoadFromFile(CurrentFilename);
MainForm.MainImage.Picture.Assign(MainJPEG);
end
else
if CurrentFiletype = PNG then
begin
MainForm.MainImage.Picture := nil;
MainPNG.LoadFromFile(CurrentFilename);
MainForm.MainImage.Picture.Assign(MainPNG);
end
else
if CurrentFiletype = GIF then
begin
MainForm.MainImage.Picture := nil;
MainGIF.LoadFromFile(CurrentFilename);
MainForm.MainImage.Picture.Assign(MainGIF);
end;
end;
procedure NextImage; //the "NextButton" button from the GUI calls this
begin
if CurrentPos < Length(ImagesOfCurrentDir)-1 then
begin
CurrentPos := Succ(CurrentPos);
CurrentFilename := ImagesOfCurrentDir[CurrentPos].Filename;
CurrentFiletype := ImagesOfCurrentDir[CurrentPos].Filetype;
UpdateTitleBar;
LoaderThread := BeginThread(nil, 0, Addr(LoadImage), nil, 0, LoaderThreadID);
CloseHandle(LoaderThread);
while MainImageIsEmpty = true do
begin
if CurrentPos < Length(ImagesOfCurrentDir)-1 then
begin
CurrentPos := Succ(CurrentPos);
CurrentFilename := ImagesOfCurrentDir[CurrentPos].Filename;
CurrentFiletype := ImagesOfCurrentDir[CurrentPos].Filetype;
UpdateTitleBar;
LoaderThread := BeginThread(nil, 0, Addr(LoadImage), nil, 0, LoaderThreadID);
CloseHandle(LoaderThread);
end;
if CurrentPos = CurrentDirImageCount-1 then Break;
end;
end;
if CurrentPos = CurrentDirImageCount-1 then
begin
MainForm.NextButton.Enabled := false;
MainForm.EndButton.Enabled := false;
MainForm.SlideshowButton.Enabled := false;
MainForm.SlideshowIntervalUpDown.Enabled := false;
end;
MainForm.PrevButton.Enabled := true;
MainForm.StartButton.Enabled := true;
end;
procedure PrevImage; //called by "PrevButton"
begin
//some code, calls LoadImage
//almost the same logic as above for a backward step among the images
end;
procedure FirstImage; //called by "StartButton"
begin
//some code, calls LoadImage
end;
procedure LastImage; //called by "EndButton"
begin
//some code, calls LoadImage
end;
procedure Slideshow; //procedure #2 which should be used in a thread
begin
while SlideshowOn = true do
begin
SlideshowInterval := MainForm.SlideshowIntervalUpDown.Position*1000;
Sleep(SlideshowInterval);
NextImage; //NextImage calls LoadImage which should be a thread
if CurrentPos = CurrentDirImageCount-1 then SlideshowOn := false;
end;
end;
function MainImageIsEmpty;
begin
if MainForm.MainImage.Picture = nil then MainImageIsEmpty := true
else MainImageIsEmpty := false;
end;
procedure TMainForm.NextButtonClick(Sender: TObject);
begin
NextImage;
end;
procedure TMainForm.PrevButtonClick(Sender: TObject);
begin
PrevImage;
end;
procedure TMainForm.StartButtonClick(Sender: TObject);
begin
FirstImage;
end;
procedure TMainForm.EndButtonClick(Sender: TObject);
begin
LastImage;
end;
procedure TMainForm.SlideshowButtonClick(Sender: TObject);
begin;
if SlideshowOn = false then
begin
SlideshowOn := true;
SlideshowThread := BeginThread(nil, 0, Addr(Slideshow), nil, 0, SlideshowThreadID);
SlideshowButton.Caption := '||';
SlideshowButton.Hint := 'DIAVETÍTÉS LEÁLLÍTÁSA';
end
else
begin
SlideshowOn := false;
CloseHandle(SlideshowThread);
SlideshowButton.Caption := '|>';
SlideshowButton.Hint := 'DIAVETÍTÉS INDÍTÁSA';
end;
end;
Run Code Online (Sandbox Code Playgroud)
这里有很多文字,而且代码不多.使用更多代码和更少文本,您的问题可能会更好.
无论如何,我可以提供一些提示.
首先,CreateThread直接调用是一种在Delphi中进行线程化的相当费力的方法.它更容易使用TThread,它以更典型的Delphi代码风格的方式包装一些低级Windows API问题.当然,你可以进一步使用像OmniThreadLibrary这样的线程库,但是现在最好坚持TThread并找出如何以这种方式去做.
现在,这不是你的问题.几乎可以肯定,您的问题将由线程的两个常见问题之一引起:
处理问题1的最常用方法是调用TThread.Synchronize或TThread.Queue从工作线程调用以强制所有VCL/GUI代码在主线程上运行.当然,您需要确保工作线程中的任何耗时代码都不使用VCL/GUI对象,因为这注定要失败.
问题2可以通过使用InterlockedXXX系列函数的关键部分或无锁方法等同步对象来处理.
究竟你的问题是我不能说的.如果您需要更详细的帮助,请发布更多代码,最有可能减少您当前正在运行的代码.