图像浏览程序 - 为什么它在线程化之后会随机崩溃?

时间:2011-07-30 20:07:25

标签: multithreading delphi image-processing windows-xp delphi-7

长话短说,我离熟练的程序员很远,事实上,到目前为止,我最复杂的程序是纯ASCII字符串操作,简单数学和数组搜索/排序,无论是在Free Pascal还是以后,Delphi 7和Java的。这是几年前,当我在高中教师(普通帕斯卡)学习编程时。后来我继续成为一名程序员(与D7和Java会面,以及一些C ++),但由于个人原因,我已经退出了编程研究,从那以后,我没有编写一行代码。

嗯,很抱歉很长时间的介绍,所以......最近我决定重新编程作为我的爱好,主要是因为我没有找到一个合适的程序来完成我想要完成的一些任务。尽管我对显式参数,指针,对象,类,构造函数和线程这些相当基本的东西的微弱理解,借助于编程书,Delphi帮助文件和互联网,我设法在Delphi 7中编写一个简单的程序可以使用外部库在给定目录中加载和显示某些图像文件格式,可以在它们之间任意切换(使用GUI),并在文本文件中记录一些信息(主要用于调试目的)。

但是,当我尝试使图像加载并显示函数线程时,我在当前版本的代码中遇到了问题。首先,为了更好地理解,我将解释我的程序的逻辑。

首先,在主窗体的FormCreate事件中,程序在当前目录(exe所在的位置)中查找支持的图像文件。如果没有图像文件,则将当前目录设置为较高级别的目录(使用标准Windows文件系统符号“..”)并检查图像。支持的图像文件由文件扩展名决定。无论如何,这个资源管理器函数在动态数组中存储找到的图像文件名和文件类型标识符(这是一个字节)。然后,使用此数组作为参考,第一个支持的图像文件由正确的库加载并显示在表单中。 GUI具有按钮和组合框以在图像之间切换,每个控件的OnClick或OnSelect(组合框)事件设置变量关于所谓的当前图像文件并调用使用参考数组的图像加载器和显示器功能。

问题是某些图像太大以至于加载需要很长时间,因此在图像完全加载和显示之前,GUI无法响应。我试图通过将每个图像加载器函数初始化为线程来使该程序成为线程。虽然GUI现在响应更快,但该程序肯定存在两个新错误。

首先,程序在更改图像时有时会随机崩溃,出现的消息要么是指“ JPEG Error#58 ”(在Delphi的内置jpeg库中,这意味着“无效的文件结构”) ),“ EAccessViolation”异常,“ EOSError”异常(包括“系统错误,代码5 ”),“未知软件异常“,”运行时错误216 “,以及有关内存位置和读取操作失败的错误消息。在使用线程之前,没有出现这些错误消息,但我当然希望(并且必须)在程序中使用线程。

另一个小错误是,当快速连续点击界面按钮时,似乎所有加载和显示都会发生,尽管是以一种迟滞然后快速的方式。我真的不知道如何“杀死”一个线程并“再次”启动它来加载现在当前的文件,而不是它试图加载几百毫秒前的过时文件。

我以下列方式开始一个帖子:

LoaderThread := CreateThread(nil, 0, Addr(LoadPicture), nil, 0, LoaderThreadID);
CloseHandle(LoaderThread);

我在主窗体的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;

2 个答案:

答案 0 :(得分:5)

这里有很多文字,而且代码不多。使用更多代码和更少文本,您的问题可能会更好。

无论如何,我可以提供一些提示。

首先,直接调用CreateThread是在Delphi中进行线程化的一种相当费力的方法。使用TThread更容易,它以更典型的Delphi代码风格的方式包装一些低级Windows API问题。当然,你可以更进一步使用像OmniThreadLibrary这样的线程库,但是现在最好坚持TThread并找出如何做到这一点。

现在,这不是你的问题。几乎可以肯定,您的问题将由线程的两个常见问题之一引起:

  1. 所有VCL和GUI代码都应该在主线程中运行。 Windows控件与创建它们的线程具有亲缘关系。 VCL的许多部分都不是线程安全的。这些问题强烈促使您将所有VCL / GUI代码放在主线程中。
  2. 由于缺乏同步,你很可能会遇到竞争状态。
  3. 处理问题1的最常见方法是从工作线程调用TThread.SynchronizeTThread.Queue,以强制所有VCL / GUI代码在主线程上运行。当然,您需要确保工作线程中的所有耗时代码都不使用VCL / GUI对象,因为这注定要失败。

    问题2可以通过使用InterlockedXXX系列函数的关键部分或无锁方法等同步对象来处理。

    你的问题究竟是什么我不能说。如果您需要更详细的帮助,请发布更多代码,很可能会减少您当前正在运行的代码。

答案 1 :(得分:1)

  1. 您创建一个线程并立即将其终止,而无需等待它完成加载
  2. LoadImage不是VCL线程安全
  3. 这里以VCL方式简单的seudo线程。代码格式简单,您可以进一步研究并进行增强

    TYourThread.Create(image file name);
    
    type
    TYourThread = class(TThread)
    protected
      FBitmap: TBitmap;
      FImageFileName: string;
      procedure BitmapToVCL;
      begin
        MainForm.MainImage.Picture := FBitmap;
      end;     
    
      procedure Execute; override;
      begin
        FBitmap := TBitmap.Create;
        try
          FBitmap.LoadFromFile(FImageFileName);
          Synchronize(BitmapToVCL);
        finally
          FreeAndNil(FBitmap);
        end;
      end;
    public
      constructor Create(const AImageFileName: string);
      begin
        FImageFileName := AImageFileName;
        inherited Create(False);
        FreeOnTerminate := True;
      end; 
    end;
    
    好运 欢呼