Программа просмотра изображений - почему происходит случайный сбой после создания потока? - PullRequest
5 голосов
/ 31 июля 2011

Короче говоря, я очень далек от опытного программиста, на самом деле мои самые сложные программы были либо простыми манипуляциями с ASCII-строками, простыми математическими вычислениями и поиском / сортировкой массивов в Free Pascal или более поздних версиях, Delphi 7 и Джава. Это было несколько лет назад, когда я изучал программирование на факультете средней школы (это был простой Паскаль). Позже я стал программистом (познакомился с D7 и Java, и немного C ++), но я бросил учебу по программированию по личным причинам, и с тех пор я не написал ни одной строчки кода.

Эмм, извините за длинное вступление, так что ... Недавно я решил возродить программирование в качестве своего хобби, главным образом потому, что не нашел подходящей программы для некоторых задач, которые я хотел бы выполнять долгое время. Несмотря на мое слабое понимание таких довольно простых вещей, как явные параметры, указатели, объекты, классы, конструкторы и потоки, с помощью книги по программированию, файлов справки Delphi и Интернета, мне удалось написать простую программу на Delphi 7. который может загружать и отображать определенные форматы файлов изображений в заданном каталоге с использованием внешних библиотек, делает возможным произвольное переключение между ними (с помощью графического интерфейса пользователя) и записывать некоторую информацию (главным образом для целей отладки) в текстовые файлы.

Тем не менее, я столкнулся с проблемой в текущей версии кода, когда я попытался сделать функцию загрузки и отображения изображения многопоточным. Во-первых, для лучшего понимания я объясню логику моей программы.

Прежде всего, в событии FormCreate главной формы программа ищет поддерживаемые файлы изображений в текущем каталоге (где находится exe). Если файлов изображений нет, текущий каталог устанавливается на верхний уровень (со стандартным символом файловой системы Windows "..") и проверяется на наличие изображений. Поддерживаемые файлы изображений определяются расширениями файлов. В любом случае, эта функция проводника сохраняет имя найденного изображения и идентификатор типа файла (который является байтом) в динамическом массиве. Затем, используя этот массив в качестве ссылки, первый поддерживаемый файл изображения загружается правильной библиотекой и отображается в форме. В графическом интерфейсе есть кнопки и поле со списком для переключения между изображениями, с переменными установки событий каждого элемента управления OnClick или OnSelect (combobox) относительно предположительно текущего файла изображения и вызова функции загрузки и отображения изображений, которая использует массив ссылок.

Проблема в том, что некоторые изображения настолько велики, что загрузка занимает заметное время, поэтому графический интерфейс не может отвечать, пока изображение не будет полностью загружено и отображено. Я попытался сделать эту программу поточной, инициализируя каждую функцию загрузчика изображений как поток. Хотя графический интерфейс стал более отзывчивым, в программе, безусловно, есть две новые ошибки.

Во-первых, программа иногда случайным образом аварийно завершает работу при смене изображений, при этом появляются сообщения, ссылающиеся либо на « Ошибка JPEG # 58 » (предположительно, означая «недопустимая структура файла» во встроенной библиотеке Delphi jpeg) , « EAccessViolation» исключение , « EOSError» исключение (включая « Системная ошибка, код 5 »), « неизвестное программное исключение » , " Ошибка выполнения 216 " и сообщения об ошибках в ячейках памяти и неудачных операциях чтения. До использования потоков ни одно из этих сообщений об ошибках не появлялось, но я, безусловно, хочу (и должен) использовать потоки в программе.

Другая, незначительная ошибка заключается в том, что при быстром последовательном нажатии на кнопки интерфейса происходит загрузка и отображение всех данных, хотя и медленным, а затем быстрым способом. У меня действительно нет идеи о том, как «убить» поток и запустить его «снова», чтобы загрузить текущий текущий файл вместо устаревшего, который он пытался загрузить несколько сотен миллисекунд назад.

Я запускаю тему следующим образом:

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

Я использую это два раза в событии FormCreate главной формы (хотя только один из них выполняется при любом запуске) и в событиях OnClick или OnSelect элементов управления GUI для облегчения выполнения желаемой функции элемента управления (например, переход кпоследнее изображение).

Есть предложения?Заранее спасибо!:)

ОБНОВЛЕНИЕ: Вот некоторые (ну, почти все) мой исходный код:

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 ]

5 голосов
/ 31 июля 2011

Здесь много текста и мало кода.Ваш вопрос, вероятно, был бы лучше с большим количеством кода и меньшим количеством текста.

В любом случае, я могу предложить несколько подсказок.

Во-первых, прямой вызов CreateThread - довольно трудоемкий способ выполнения потоков в Delphi.,Проще использовать TThread, который оборачивает некоторые проблемы низкоуровневого API Windows способом, более родным для типичного стиля кода Delphi.Конечно, вы можете пойти дальше и использовать библиотеку потоков, такую ​​как OmniThreadLibrary , но сейчас может быть лучше просто придерживаться TThread и решить, как это сделать таким образом.

Теперь это не будет вашей проблемой здесь.Почти наверняка ваша проблема будет вызвана одной из двух распространенных проблем с многопоточностью:

  1. Весь код VCL и GUI должен выполняться в основном потоке.Элементы управления Windows имеют сходство с потоком, который их создает.Многие части VCL не являются поточно-ориентированными.Эти проблемы сильно подталкивают вас к тому, чтобы поместить весь код VCL / GUI в основной поток.
  2. Вполне возможно, что у вас есть состояние гонки из-за отсутствия синхронизации.

Наиболее распространенныйЧтобы решить проблему 1, нужно вызвать TThread.Synchronize или TThread.Queue из рабочих потоков, чтобы заставить весь код VCL / GUI выполняться в основном потоке.Конечно, вы должны быть уверены, что ни один из трудоемкого кода в вашем рабочем потоке не использует объекты VCL / GUI, поскольку это обречено на неудачу.

Проблема 2 может быть решена с помощью объектов синхронизации, таких как критические секции илиметоды без блокировок, использующие семейство функций InterlockedXXX.

Точно, в чем ваша проблема, я не могу сказать.Если вам нужна более подробная помощь, пожалуйста, опубликуйте больше кода, скорее всего, сокращенного от того, что вы используете в данный момент.

1 голос
/ 31 июля 2011
  1. Вы создаете поток и сразу его убиваете, не дожидаясь окончания загрузки
  2. LoadImage не является потокобезопасным VCL

Здесь простая seudo-нить в стиле VCL. Коды в простой форме, и вы можете учиться дальше и делать улучшения

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;

Гук удачи Cheer

...