Recent

Author Topic: Simply Threading A Block Of Code?  (Read 3165 times)

FiftyTifty

  • Jr. Member
  • **
  • Posts: 56
Simply Threading A Block Of Code?
« on: October 25, 2016, 05:52:38 am »
I've made a wee program in Pascal, that iterates through all the .txt files in a given directory. It then loads a text file into a sorted TStringList, saves it, and repeats for the next .txt file.

Trouble is, I need a wee indicator telling me that it's actually doing something. I added such a thing, but the UI doesn't update whilst the pretty hefty function is running.

In C#, you'd just have to:

Spawn a thread that runs the intensive function:
Code: [Select]
Task taskLoadTextFileIntoWorksheet = Task.Factory.StartNew(() => //Code here )
And just chuck in the UI changing stuff inside a delegate procedure-ish thing:
Code: [Select]
Invoke(new MethodInvoker(delegate {//CodeGoesHere}) )
Ya can see my working example in C# 'ere: https://github.com/MajinCry/Fallout-4---Dialog-Renamer/blob/master/WindowsFormsApplication3/Form1.cs


Luckily, my Pascal program is waaaaay simpler.

https://github.com/MajinCry/Multiple-Text-File-Sorter/blob/master/programwindow.pas
https://github.com/MajinCry/Multiple-Text-File-Sorter/blob/master/mainprogramcode.pas

ProgramWindow contains all the UI guff, with MainProgramCode doing all the work. What I'd like to do, is chuck the
Code: [Select]
SortTextFilesInDir() function into it's own thread, so the UI doesn't hang, and then chuck in
Code: [Select]
editToUpdate.Text := tstrlistTextFilesInDir[iCounter]; into a delegate, to update the UI.

But this isn't C#, and the Lazarus wiki is down. What do?

One of the techniques I've read, that doesn't rely on poorly-optimized boilerplate, is to use an event messaging system (w/e that means). No idea about that stuff, 'tis way over my head, so I'd need some pretty dang fine hand holding with that.

I'd guess that it's something along the lines of creating a listener in ProgramWindow.pas, that has a call to
Code: [Select]
procedure OnUpdateCurrentTextFileDisplayed(strReceivedFilename), which would have the
Code: [Select]
editToUpdate.Text := tstrlistTextFilesInDir[iCounter]; line inside it.

But again, no idea on how to actually do that.
« Last Edit: October 25, 2016, 01:35:02 pm by MajinCry »

serbod

  • Full Member
  • ***
  • Posts: 142
Re: Simpley Threading A Block Of Code?
« Reply #1 on: October 25, 2016, 09:21:56 am »
Define global variable:
Code: Pascal  [Select][+][-]
  1. var GlobalProgress: Integer;

Inside threads do increment that variable on every iteration with thread-safe InterlockedIncrement function:
Code: Pascal  [Select][+][-]
  1. InterlockedIncrement(GlobalProgress);

Place TTimer on from, set iterval to 500 and write OnTimer event, that read progress status from from global variable.
Code: Pascal  [Select][+][-]
  1. ProgressBar.Position := GlobalProgress;


FiftyTifty

  • Jr. Member
  • **
  • Posts: 56
Re: Simpley Threading A Block Of Code?
« Reply #2 on: October 25, 2016, 01:22:22 pm »
That's a wee bit too abstract for me. Need some proper step-by-step instructions for this.

However, I did find this wee nugget on the wiki: http://wiki.freepascal.org/Multithreaded_Application_Tutorial#Lazarus_Widgetset_Interfaces

Not too sure on what makes the procedure run when the message is set. Does appending the statement message make it run, whenever a specific variable is passed?

Edit: So right from the get-go, adding the CThreads, CMem and Interface units makes the program throw a fit.

Code: [Select]
unit programwindow;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  CThreads, CMem, Interfaces,
  MainProgramCode;

var
   strProgress: string;

type

  { TformProgramWindow }

  TformProgramWindow = class(TForm)
    buttonBegin: TButton;
    buttonGetFolder: TButton;
    checkboxDoSubdirs: TCheckBox;
    editDisplayCurrentFile: TEdit;
    editGetFolder: TEdit;
    groupboxGetFilesInDir: TGroupBox;
    dialogSelectTextFile: TOpenDialog;
    procedure buttonBeginClick(Sender: TObject);
    procedure buttonGetFolderClick(Sender: TObject);
  private
         procedure OnProgressStringUpdate(var msgString: TLMessage); message strProgress;
  public
    { public declarations }
  end;

var
  formProgramWindow: TformProgramWindow;

implementation

{$R *.lfm}

{ TformProgramWindow }

procedure TformProgramWindow.buttonGetFolderClick(Sender: TObject);
begin
     if dialogSelectTextFile.Execute() then
        if FileExists(dialogSelectTextFile.FileName) then
           editGetFolder.Text := ExtractFilePath(dialogSelectTextFile.FileName);
end;

procedure TformProgramWindow.buttonBeginClick(Sender: TObject);
begin

     if not DirectoryExists(editGetFolder.Text) then begin
        ShowMessage('Directory does not exist');
        exit;
     end;

     SortTextFilesInDir(editGetFolder.Text, '*.txt', checkboxDoSubdirs.Checked,
                        editDisplayCurrentFile);
     ShowMessage('Done!');
end;

end.

Quote
programwindow.pas(9,3) Fatal: Cannot find CThreads used by programwindow of the Project Inspector.
« Last Edit: October 25, 2016, 01:36:50 pm by MajinCry »

Leledumbo

  • Hero Member
  • *****
  • Posts: 8757
  • Programming + Glam Metal + Tae Kwon Do = Me
Re: Simpley Threading A Block Of Code?
« Reply #3 on: October 25, 2016, 03:15:36 pm »
Edit: So right from the get-go, adding the CThreads, CMem and Interface units makes the program throw a fit.
  • Do NOT put CThreads and/or CMem (not needed, no idea why you put it here) in unit uses clause, do it in program uses clause and make them listed FIRST. Otherwise, there could be memory manager change in the middle resulting in undefined behavior.
  • Cthreads is only used on *nix, hence the default Lazarus program protects it with ifdef Unix. I believe you're on Windows since you mention C#

FiftyTifty

  • Jr. Member
  • **
  • Posts: 56
Re: Simpley Threading A Block Of Code?
« Reply #4 on: October 25, 2016, 03:23:47 pm »
  • Do NOT put CThreads and/or CMem (not needed, no idea why you put it here) in unit uses clause, do it in program uses clause and make them listed FIRST. Otherwise, there could be memory manager change in the middle resulting in undefined behavior.
  • Cthreads is only used on *nix, hence the default Lazarus program protects it with ifdef Unix. I believe you're on Windows since you mention C#

I'm an eejit. I just read this line:
Quote
So, your Lazarus application code should look like:

And assumed that I had ta chuck that in. And aye, I'm on Windows 7, but cross-platform functionality is always nice ta have.

Removed those Uses tidbits so it looks like this:
Code: [Select]
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  MainProgramCode;   

and I get this error:

Quote
Compile Project, Target: MultipleTextFileSorter.exe: Exit code 1, Errors: 2
programwindow.pas(29,58) Error: Identifier not found "TLMessage"
programwindow.pas(29,89) Error: Illegal expression after message directive

 

TinyPortal © 2005-2018