merge paint results in thread bitmap painting - multithreading

I want to speed up painting a bitmap, therefore I designed a class like BITMAP THREAD CLASS. Once the individual painting of a partial image is finished I want to merge all image in the Thread.done procedure
My code goes like this
type
TbmpthreadForm = class(TForm)
.....
THreadImage: TImage;
procedure Button_threadstartClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private-Deklarationen }
procedure ThreadDone(Sender: TObject);
public
{ Public-Deklarationen }
fserver, fdatabasename, ftablename: String;
global_thread_counter: Integer;
XPixel, YPixel: Integer;
Masterbitmap: TBitmap;
end;
var
bmpthreadForm: TbmpthreadForm;
implementation
{$R *.dfm}
procedure TbmpthreadForm.ThreadDone(Sender: TObject);
begin
dec(global_thread_counter);
MyStatusBar.SimpleText := 'Thread Count ->' + IntToStr(global_thread_counter);
Masterbitmap.Canvas.Lock;
with (Sender as TPaintBitmapThread) do
begin
bitblt(Masterbitmap.Canvas.handle, 0, 0, XPixel, YPixel,
bitmap.Canvas.handle, 0, 0, srcand);
THreadImage.Picture.Bitmap.Assign(Masterbitmap);
// lets see local tthread intermediate results and save it to HD
THreadImage.Picture.Bitmap.SaveToFile('c:\temp\myimage' + IntToStr(Index)
+ '.bmp');
end;
Masterbitmap.Canvas.UnLock;
if (global_thread_counter = 0) then
begin
...
end;
end;
procedure TbmpthreadForm.Button_threadstartClick(Sender: TObject);
var
.....
begin
index_max := 2000000;
threadCounter := 10;
Indexdelta := round(index_max / threadCounter);
///
///
....
Masterbitmap.Width := XPixel;
Masterbitmap.Height := YPixel;
for i := 0 to threadCounter - 1 do
begin
n := i * Indexdelta;
m := (i + 1) * Indexdelta;
// just a test sql string ....
sqlstr := 'select * from Mytable where objectindex <' + IntToStr(m) +
' and Objectindex >' + IntToStr(n);
aPaintBitmapThread := TPaintBitmapThread.Create(XPixel, YPixel, ...... , fserver, fdatabasename, ftablename,
sqlstr, i);
aPaintBitmapThread.OnTerminate := ThreadDone;
Memo1.Lines.Add('start thread->' + IntToStr(i));
inc(global_thread_counter);
end;
end;
The Thread.done design follows a previous topic here on SO ( reference question
As the resulting image/Masterbitmap looks a bit different from run to run , I guess my approach is not thread safe design for copy Thread bmp content into the masterbitmap in the VCL mainform,
I can not see any error in my code, what is wrong ????
additional question
Q1 : fbitmap inside TPaintBitmapThread is created inside the Thread.create procedure, for TAdoconnection I found the comment, it should be created inside the thread.execute. Must this also be done the the bitmap ?
Q2 : the attached image shows the expected result of an image(Bitmap) by an Thread and the actual image results (as seen by the THreadImage.Picture.Bitmap.SaveToFile command)

bitblt(Masterbitmap.Canvas.handle, 0, 0, XPixel, YPixel,
bitmap.Canvas.handle, 0, 0, srcand);
you explicitly called Masterbitmap.Canvas.Lock, however you didn't call bitmap.Canvas.Lock (so you can loose the canvas handle anytime within this call...)
Additionally, you need to consider thread safety within GDI itself: Sharing of any GDI objects between different threads should be avoided at all cost. For example if you select a bitmap into two different device contexts at the same time (but in different threads) you may run into problems within GDI itself...
Please note that older versions of delphi don't protect against sharing of cached handles (Font, Brush and Pen handles are cached within a global list. This was fixed in an XE3 service pack if I remember correctly).
In short: I'd consider to avoid TCanvas and TBitmap completely if you really need multi-threading. (It's much easier to be multi-threading safe that way)

Related

How to update GUI controls with versions of Delphi (Pre Delphi 2010) without Synchronize

I have some apps built with Delphi 2010 and XE4 that use Synchronize in a thread. I think Synchronize was introduced to Delphi in Delphi 2010. My thread operates very well so that is not the problem.
My question is: Is there any way to "Synchronize" with versions of Delphi prior to Delphi 2010 or to ask it in a different way, how do you update GUI controls in these earlier versions of Delphi without Synchronize?
The code shown below is a subset of the actual code to reduce the length of this post.
type
{ A TThread descendent for loading Images from a folder }
TFolderLoadingThread = class(TThread)
private
{ Private declarations }
AspectRatio: double;
protected
{ Protected declarations }
procedure Execute; override;
public
{ Public declarations }
constructor Create(CreateSuspended: Boolean);
end;
procedure TFolderLoadingThread.Execute;
{ Load images ImageEnView in the thread. }
begin
inherited;
{ Free the thread onTerminate }
FreeOnTerminate := True;
if not Terminated then
begin
{ Set the Progressbar.Max Value }
**Synchronize**(
procedure
begin
if iFileCount > 0 then
Form1.Gauge1.MaxValue := iFileCount - 1;
end);
end;
Synchronize is very old routine, but there are no anonymous procedures in Delphi versions prior to D2009. Synchronize was intended to call a method without parameters in these versions.
procedure TFolderLoadingThread.UpdateProgress;
begin
if iFileCount > 0 then
Form1.Gauge1.MaxValue := iFileCount - 1;
end;
in Execute:
do thead work...
Synchronize(UpdateProgress);
P.S. You have not to call Terminate in the Execute body
Synchronize was there almost forever. I used it in Delphi 5. But that already was told above.
You can also use Windows API (namely PostMessage) to do it, if you can afford the change happening "soon after", in other words if your thread does not have to stop and wait until GUI updated. For example if there is a long calculations, and you want to have a gauge "1234 of 5678 complete" - then there is little point to stop the calculation processes. Let the counter would be inaccurate, plus-minus a dozen. Why care ?
Now, how to implement it... For simplistic cases you can use direct Windows access.
For example, if we have a windowed text label - the one inherited from TWinControl
http://docwiki.embarcadero.com/Libraries/XE4/en/Vcl.StdCtrls.TStaticText
Then we can bypass VCL and all its goodies and change the caption with a standard Windows API
http://msdn.microsoft.com/en-us/library/windows/desktop/ms632644.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms644944.aspx
You would also have to make a proper calculation of total numbers and messages to display. Yet this is not related to GUI and VCL. But hopefully you know about synchronization primitives, for example
http://www.freepascal.org/docs-html/fcl/syncobjs/tcriticalsection.html
AS. Yes, there is AtomicIncrement for you, i just show the framework in details.
There below i assume TMyThread to be one of the pool of workers, processing some common set of work items. It might be the only worker thread or one of many, it just does not know it, nor care about.
TThreadsCoordinator = class
...
private
CounterCS; TCriticalSection;
public
property CompleteItemsCount: integer
read FCompleteItemsCount;
function IncCompleteItemsCount(const Delta: integer): integer;
end;
...
function TThreadsCoordinator.IncCompleteItemsCount;
begin
CounterCS.Acquire;
try
Inc(FCompleteItemsCount, Delta);
finally
CounterCS.Release;
end;
Result := FCompleteItemsCount;
end;
var GlobalCaptionBuffer: array[0..127] of char;
// should be filled through with #0 before threads are started
procedure TMyThread.WorkItemComplete;
var s: string;
begin
...
s := Format('Done: %d of %d',
[ Self.Coordinator.IncCompleteItemsCount( +1 ),
Self.Coordinator.TotalItemsCount ] );
StrCopy( #GlobalCaptionBuffer[0], PChar(s));
if MyProgressForm1.Visible then // avoid ReCreateWnd inside .ShowModal
PostMessage( MyProgressForm1.StaticText1.Handle,
WM_SETTEXT, 0, Integer(#GlobalCaptionBuffer[0]));
...
end;
This implementation - while being straight-forward - may suffer from synchronization issues.
* you need some global buffer for the text, that would not be deleted before WM_SETTEXT would actually be received and executed.
* you need to think what would happen, when the text in that buffer would be in process of updating when the label would read it simultaneously
* David Heffernan below claims that in some conditions the Windows controls might be in process of destruction and re-creation (read: RecreateWnd method), thus unexpected consequences might happen. Personally - as long as that ProgressForm would only be controlled by WinGDI meeans avoiding all VCL goodies (such as changing combobox styles on the go, which does trigger RecreateWnd, but which is not possible by native WinGDI API) - i cannot see why that can happen. But David claims it can. Choose for yourself, if that suits you.
However for more complex tasks (or for using window-less text labels) you can resort to "message methods" - the foundation of VCL.
http://www.freepascal.org/docs-html/ref/refsu31.html
http://docwiki.embarcadero.com/RADStudio/XE4/en/Methods
This implementation is more flexible (separating data generation and data visualiation) and arguable more reliable (see RecreateWnd remarks in comments). But it need more of a boilerplate code.
const WM_IncCounter = WM_USER + 10;
type TMyProgressForm = class(TForm)
private
FCompleteItemsCount: integer;
procedure IncCounter(var Msg: TMessage ); message WM_IncCounter;
...
end;
procedure TMyProgressForm.IncCounter(var Msg: TMessage );
var s: string;
begin
Inc(FCompleteItemsCount, Msg.WParam);
s := Format('Done: %d of %d',
[ FCompleteItemsCount, TotalItemsCount ] );
Label1.Caption := s;
ProgressBar1.Position := FCompleteItemsCount;
end;
procedure TMyThread.WorkItemComplete;
begin
Inc(Self.UncommittedTicks);
if MyProgressForm1.Visible then // avoid ReCreateWnd inside .ShowModal
begin
PostMessage( MyProgressForm1.Handle, WM_IncCounter, Self.UncommittedTicks, 0);
Self.UncommittedTicks := 0;
end;
end;

Multithreaded file upload synchronization

Currently I am working on a Delphi XE3 client/server application to transfer files (with the Indy FTP components). The client part monitors a folder, gets a list of the files inside, uploads them to the server and deletes the originals. The uploading is done by a separate thread, which processes files one by one. The files can range from 0 to a few thousand and their sizes also vary a lot.
It is a Firemonkey app compiled for both OSX and Windows, so I had to use TThread instead of OmniThreadLibrary, which I preferred. My customer reports that the application randomly freezes. I could not duplicate it, but since I don't have so much experience with TThread, I might have put deadlock condition somewhere. I read quite a lot of examples, but I'm still not sure about some of the multithread specifics.
The app structure is simple:
A timer in the main thread checks the folder and gets information about each file into a record, which goes into a generic TList. This list keeps information about the names of the files, size, the progress, whether the file is completely uploaded or has to be retried. All that is displayed in a grid with progress bars, etc. This list is accessed only by the main thread.
After that the items from the list are sent to the thread by calling the AddFile method (code below). The thread stores all files in a thread-safe queue like this one http://delphihaven.wordpress.com/2011/05/06/using-tmonitor-2/
When the file is uploaded the uploader thread notifies the main thread with a call to Synchronize.
The main thread periodically calls the Uploader.GetProgress method to check the current file progress and display it. This function is not actually thread-safe, but could it cause a deadlock, or only wrong data returned?
What would be a safe and efficient way to do the progress check?
So is this approach OK or I have missed something? How would you do this?
For example I though of making a new thread just to read the folder contents. This means that the TList I use has to be made thread-safe, but it has to be accessed all the time to refresh the displayed info in the GUI grid. Wouldn't all the synchronization just slow down the GUI?
I have posted the simplified code below in case someone wants to look at it. If not, I would be happy to hear some opinions on what I should use in general. The main goals are to work on both OSX and Windows; to be able to display information about all the files and the progress of the current one; and to be responsive regardless of the number and size of the files.
That's the code of the uploader thread. I have removed some of it for easier reading:
type
TFileStatus = (fsToBeQueued, fsUploaded, fsQueued);
TFileInfo = record
ID: Integer;
Path: String;
Size: Int64;
UploadedSize: Int64;
Status: TFileStatus;
end;
TUploader = class(TThread)
private
FTP: TIdFTP;
fQueue: TThreadedQueue<TFileInfo>;
fCurrentFile: TFileInfo;
FUploading: Boolean;
procedure ConnectFTP;
function UploadFile(aFileInfo: TFileInfo): String;
procedure OnFTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
procedure SignalComplete;
procedure SignalError(aError: String);
protected
procedure Execute; override;
public
property Uploading: Boolean read FUploading;
constructor Create;
destructor Destroy; override;
procedure Terminate;
procedure AddFile(const aFileInfo: TFileInfo);
function GetProgress: TFileInfo;
end;
procedure TUploader.AddFile(const aFileInfo: TFileInfo);
begin
fQueue.Enqueue(aFileInfo);
end;
procedure TUploader.ConnectFTP;
begin
...
FTP.Connect;
end;
constructor TUploader.Create;
begin
inherited Create(false);
FreeOnTerminate := false;
fQueue := TThreadedQueue<TFileInfo>.Create;
// Create the TIdFTP and set ports and other params
...
end;
destructor TUploader.Destroy;
begin
fQueue.Close;
fQueue.Free;
FTP.Free;
inherited;
end;
// Process the whole queue and inform the main thread of the progress
procedure TUploader.Execute;
var
Temp: TFileInfo;
begin
try
ConnectFTP;
except
on E: Exception do
SignalError(E.Message);
end;
// Use Peek instead of Dequeue, because the item should not be removed from the queue if it fails
while fQueue.Peek(fCurrentFile) = wrSignaled do
try
if UploadFile(fCurrentFile) = '' then
begin
fQueue.Dequeue(Temp); // Delete the item from the queue if succesful
SignalComplete;
end;
except
on E: Exception do
SignalError(E.Message);
end;
end;
// Return the current file's info to the main thread. Used to update the progress indicators
function TUploader.GetProgress: TFileInfo;
begin
Result := fCurrentFile;
end;
// Update the uploaded size for the current file. This information is retrieved by a timer from the main thread to update the progress bar
procedure TUploader.OnFTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
fCurrentFile.UploadedSize := AWorkCount;
end;
procedure TUploader.SignalComplete;
begin
Synchronize(
procedure
begin
frmClientMain.OnCompleteFile(fCurrentFile);
end);
end;
procedure TUploader.SignalError(aError: String);
begin
try
FTP.Disconnect;
except
end;
if fQueue.Closed then
Exit;
Synchronize(
procedure
begin
frmClientMain.OnUploadError(aError);
end);
end;
// Clear the queue and terminate the thread
procedure TUploader.Terminate;
begin
fQueue.Close;
inherited;
end;
function TUploader.UploadFile(aFileInfo: TFileInfo): String;
begin
Result := 'Error';
try
if not FTP.Connected then
ConnectFTP;
FUploading := true;
FTP.Put(aFileInfo.Path, ExtractFileName(aFileInfo.Path));
Result := '';
finally
FUploading := false;
end;
end;
And parts of the main thread that interact with the uploader:
......
// Main form
fUniqueID: Integer; // This is a unique number given to each file, because there might be several with the same names(after one is uploaded and deleted)
fUploader: TUploader; // The uploader thread
fFiles: TList<TFileInfo>;
fCurrentFileName: String; // Used to display the progress
function IndexOfFile(aID: Integer): Integer; //Return the index of the record inside the fFiles given the file ID
public
procedure OnCompleteFile(aFileInfo: TFileInfo);
procedure OnUploadError(aError: String);
end;
// This is called by the uploader with Synchronize
procedure TfrmClientMain.OnUploadError(aError: String);
begin
// show and log the error
end;
// This is called by the uploader with Synchronize
procedure TfrmClientMain.OnCompleteFile(aFileInfo: TFileInfo);
var
I: Integer;
begin
I := IndexOfFile(aFileInfo.ID);
if (I >= 0) and (I < fFiles.Count) then
begin
aFileInfo.Status := fsUploaded;
aFileInfo.UploadedSize := aFileInfo.Size;
FFiles.Items[I] := aFileInfo;
Inc(FFilesUploaded);
TFile.Delete(aFileInfo.Path);
colProgressImg.UpdateCell(I);
end;
end;
procedure TfrmClientMain.ProcessFolder;
var
NewFiles: TStringDynArray;
I, J: Integer;
FileInfo: TFileInfo;
begin
// Remove completed files from the list if it contains more than XX files
while FFiles.Count > 1000 do
if FFiles[0].Status = fsUploaded then
begin
Dec(FFilesUploaded);
FFiles.Delete(0);
end else
Break;
NewFiles := TDirectory.GetFiles(WatchFolder, '*.*',TSearchOption.soAllDirectories);
for I := 0 to Length(NewFiles) - 1 do
begin
FileInfo.ID := FUniqueID;
Inc(FUniqueID);
FileInfo.Path := NewFiles[I];
FileInfo.Size := GetFileSizeByName(NewFiles[I]);
FileInfo.UploadedSize := 0;
FileInfo.Status := fsToBeQueued;
FFiles.Add(FileInfo);
if (I mod 100) = 0 then
begin
UpdateStatusLabel;
grFiles.RowCount := FFiles.Count;
Application.ProcessMessages;
if fUploader = nil then
break;
end;
end;
// Send the new files and resend failed to the uploader thread
for I := 0 to FFiles.Count - 1 do
if (FFiles[I].Status = fsToBeQueued) then
begin
if fUploader = nil then
Break;
FileInfo := FFiles[I];
FileInfo.Status := fsQueued;
FFiles[I] := FileInfo;
SaveDebug(1, 'Add: ' + ExtractFileName(FFiles[I].Path));
FUploader.AddFile(FFiles[I]);
end;
end;
procedure TfrmClientMain.tmrGUITimer(Sender: TObject);
var
FileInfo: TFileInfo;
I: Integer;
begin
if (fUploader = nil) or not fUploader.Uploading then
Exit;
FileInfo := fUploader.GetProgress;
I := IndexOfFile(FileInfo.ID);
if (I >= 0) and (I < fFiles.Count) then
begin
fFiles.Items[I] := FileInfo;
fCurrentFileName := ExtractFileName(FileInfo.Path);
colProgressImg.UpdateCell(I);
end;
end;
function TfrmClientMain.IndexOfFile(aID: Integer): Integer;
var
I: Integer;
begin
Result := -1;
for I := 0 to FFiles.Count - 1 do
if FFiles[I].ID = aID then
Exit(I);
end;
This may not be the problem, but TFileInfo is a record.
This means that when passed as a (non const/var) parameter, it gets copied. This can result in issues with things like strings in the record which don't get reference counts updated when the record is copied.
One thing to try would be to make it a class and pass an instance as the parameter (i.e. a Pointer to the data on the heap).
Something else to watch out for is shared Int64's (e.g. your size values) on threaded 32bit systems.
Updating/reading these is not done atomically & you don't have any specific protections, so it is possible for a read of the value to get mismatched upper and lower 32-bits due to threading. (e.g. Read Upper 32 bits, Write Upper 32bits, Write lower 32bits, Read Lower 32bits, with reads & write in different threads). This is probably not causing the problems you are seeing and unless you are working with files transfers of > 4GB, unlikely to ever cause you any issues.
Deadlocks are definitely hard to spot, but this may be the problem.
In your code, I didn't see that you added any timeout to the enqueue, peek or dequeue - which means it will take the default of Infinite.
The enqueue has this line in it - meaning, like any synchronization object, it will block until either the Enter completes (it locks the monitor) or the Timeout occurs (since you don't have a timeout, it will wait forever)
TSimpleThreadedQueue.Enqueue(const Item: T; Timeout: LongWord): TWaitResult;
...
if not TMonitor.Enter(FQueue, Timeout)
I'm also going to make the assumption that you implemented PEEK yourself based on the Dequeue - only you don't actually remove the item.
That appears to implement its own timeout - however, you still have the following:
function TSimpleThreadedQueue.Peek/Dequeue(var Item: T; Timeout: LongWord): TWaitResult;
...
if not TMonitor.Enter(FQueue, Timeout)
Where timeout is Infinite - so, if you are in the peek method waiting for it to be signaled with an infinite timeout, then you can't Enqueue something from a second thread without blocking that thread waiting for the peek method to become complete on an infinite timeout.
Here is a snippet of the comment from TMonitor
Enter locks the monitor object with an optional timeout (in ms) value.
Enter without a timeout will wait until the lock is obtained.
If the procedure returns it can be assumed that the lock was acquired.
Enter with a timeout will return a boolean status indicating whether or
not the lock was obtained (True) or the attempt timed out prior to
acquire the lock (False). Calling Enter with an INFINITE timeout
is the same as calling Enter without a timeout.
Since the implementation uses Infinite by default, and a TMonitor.Spinlock value is not provided, that will block the thread until it can acquire the FQueue object.
My suggestion would be to change your code as follows:
// Use Peek instead of Dequeue, because the item should not be removed from the queue if it fails
while true do
case fQueue.Peek(fCurrentFile,10)
wrSignaled:
try
if UploadFile(fCurrentFile) = '' then
begin
fQueue.Dequeue(Temp); // Delete the item from the queue if succesful
SignalComplete;
end;
except
on E: Exception do
SignalError(E.Message);
end;
wrTimeout: sleep(10);
wrIOCompletion,
wrAbandoned,
wrError: break;
end; //case
This way, peek won't hold the lock on FQueue indefinitely, leaving a window for the Enqueue to acquire it and add the file from the main (UI) thread.
This might be a long shot, but here is another possibility [the former answer may be more likely] (something I just ran across, but had known before): The use of Synchronize may be causing the deadlock. Here is a blog about why this happens:
Delphi-Workaround-for-TThread-SynchronizeWaitFor-.aspx
The pertinent point from the article:
Thread A calls Synchronize(MethodA)
Thread B calls Synchronize(MethodB)
Then, inside the context of the Main Thread:
Main thread calls CheckSynchronize() while processing messages
CheckSynchronize is implemented to batch-process all waiting calls(*). So it picks up the
queue of waiting calls (containing MethodA and MethodB) and loops
through them one by one.
MethodA executes in the main thread's
context. Assume MethodA calls ThreadB.WaitFor
WaitFor calls
CheckSynchronize to process any waiting calls to Synchronize
In theory, this should then process ThreadB's Synchronize(MethodB),
allowing Thread B to complete. However, MethodB is already a
possession of the first CheckSynchronize call, so it never gets
called.
DEADLOCK!
Embarcadero QC article describing the problem in more detail.
While I don't see any ProcessMessages calls in the above code, or for that matter, a WaitFor that would be called during a Synchronize, it could still be a problem that at the point a synchronize is called, another thread calls the synchronize as well - but the main thread has already synchronized and is blocking.
This didn't click with me at first, because I tend to avoid Synchronize calls like the plague and usually design UI updates from threads using other methods like message passing and thread safe lists with message notification instead of synchronize calls.

Is this the correct way to set a thread's name in Delphi 6?

I want to have a nice user friendly name appear in the thread list window while in the Delphi 6 IDE. I found the code below on the web for doing this in Delphi 6 since as far as I know that version does not have SetThreadName() implemented natively. I call it from my thread's Execute() method. I know it's being called because the IDE pops up when the Exception is raised. However, when I look in the thread list (Ctrl + Alt + T), I don't see the name I set. I just see the usual Thread Id, State, Status, and Location columns, nothing else.
What do I need to do differently to get the thread names to appear? Also, does anyone have an idea on how to stop the IDE from pausing on the RaiseException line? I have a lot of threads in the program and it's annoying to have the IDE popping up N times every time I run the program.
I know I can disable the IDE from stopping on Delphi Exceptions, but I want that normally and I'd prefer not to have to toggle that off and on every time a new set of threads is created.
Named threads in Delphi - what is that for?
procedure SetThreadName_delphi(const Name: string);
type
TThreadNameInfo =
record
RecType: LongWord;
Name: PChar;
ThreadID: LongWord;
Flags: LongWord;
end;
var
info:TThreadNameInfo;
begin
// This code is extremely strange, but it's the documented way of doing it!
info.RecType := $1000;
info.Name := PChar(Name);
info.ThreadID := $FFFFFFFF;
info.Flags := 0;
try
RaiseException($406D1388, 0,
SizeOf(info) div SizeOf(LongWord), PDWord(#info));
except
end;
end;
I have found the original code
It is an application-specific exception (that means it is specific for Visual C++ compiler). I see no reason why Delphi should support this strange feature (though it is possible).
Edit : BUT IT WORKS! (Thanks to Remy Lebeau)
Just tested on Delphi XE (I see 'Wow!' in the debugger 'Thread status' window):
unit NameTest;
interface
uses
Windows, Classes;
type
TTestThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{ TTestThread }
procedure SetThreadName_delphi(const Name: string);
type
TThreadNameInfo =
record
RecType: LongWord;
Name: PAnsiChar;
ThreadID: LongWord;
Flags: LongWord;
end;
var
info:TThreadNameInfo;
AnsiName: AnsiString;
begin
AnsiName:= Name;
info.RecType := $1000;
info.Name := PAnsiChar(AnsiName);
info.ThreadID := $FFFFFFFF;
info.Flags := 0;
try
RaiseException($406D1388, 0,
SizeOf(info) div SizeOf(LongWord), PDWord(#info));
except
end;
end;
procedure TTestThread.Execute;
begin
SetThreadName_delphi('Wow!');
while not Terminated do
Sleep(1000);
end;
end.
In C++Builder 6 and Delphi 7 onwards, the File > New > Other > Thread Object wizard has an option for naming the new thread in the debugger. The wizard generates a stub TThread descendant class with the necessary RaiseException() implementation at the top of its Execute() method.
That is not any help for Delphi 6 though, which did not support thread naming yet.

Selective critical section - conditional

I got a thread which takes a db table as a paramater, I got an issue where I can't write to that db table at the same time.
1 instance of TMyThread can have a db table of 'Member' while another could have 'Staff' however there can be cases of two threads open with the same table.
Thus, I need to wrap the code in a critical section (or similar) but I don't want some dirty thing like several crical sections like (fMemberTable, fStaffTable)...
begin
if fDBTable = 'Member' then
fMemberTable.Enter
else if fDbTable = 'Staff' then
....
We have 8 tables so that would get messy
Is there some way to do
TCricalSection(fMemberTable).Enter;
Or some way to do this which is easy to 'scale' and use?
One critical section around the function doesn't make sense, as I don't want to hold back other tables....
You can do:
TMonitor.Enter(fMemberTable);
try
// Do your stuff
finally TMonitor.Exit(fMemberTable);
end;
Please note this is a SPIN LOCK, not a true critical section. Very practical if you're not going to have a lot of collisions, but if threads block each other regularly, you might want to fall back to the critical section. The spin lock is, by definition, a busy-wait lock.
but I'm not sure what version of Delphi introduced this and you don't have version-specific tags.
You can use a Critical Section list, for example, My class defined in this unit:
interface
uses Classes, SyncObjs;
type
{ TCriticalSectionList by jachguate }
{ http://jachguate.wordpress.com }
TCriticalSectionList = class
private
FCSList: TThreadList;
FNameList: TStringList;
function GetByName(AName: string): TCriticalSection;
public
constructor Create();
destructor Destroy(); override;
property ByName[AName: string]: TCriticalSection read GetByName; default;
end;
function CSList: TCriticalSectionList;
implementation
uses SysUtils;
{ TCriticalSectionList }
constructor TCriticalSectionList.Create;
begin
inherited;
FCSList := TThreadList.Create;
FNameList := TStringList.Create;
end;
destructor TCriticalSectionList.Destroy;
var
I: Integer;
AList: TList;
begin
AList := FCSList.LockList;
for I := AList.Count - 1 downto 0 do
TCriticalSection(AList[I]).Free;
FCSList.Free;
FNameList.Free;
inherited;
end;
function TCriticalSectionList.GetByName(AName: string): TCriticalSection;
var
AList: TList;
AIdx: Integer;
begin
AList := FCSList.LockList;
try
AName := UpperCase(AName);
AIdx := FNameList.IndexOf(AName);
if AIdx < 0 then
begin
FNameList.Add(AName);
Result := TCriticalSection.Create;
AList.Add(Result);
end
else
Result := AList[AIdx];
finally
FCSList.UnlockList;
end;
end;
var
_CSList: TCriticalSectionList;
function CSList: TCriticalSectionList;
begin
if not Assigned(_CSList) then
_CSList := TCriticalSectionList.Create;
Result := _CSList;
end;
initialization
_CSList := nil;
finalization
_CSList.Free;
end.
The class basically define a List of critical sections, accesible by "name". The first time you ask for a Critical section of a particular name that critical section is automatically created for you. You must access a single instance of this class, use the provided CSList function.
All critical sections are destroyed when the instance of the list is destroyed, for instance, the "default" instance is destroyed upon application end.
You can write code like this example:
begin
CSList[fDBTable].Enter;
try
DoStuff;
finally
CSList[fDBTable].Leave;
end;
end;
Enjoy.

BeginThread Structure - Delphi

I've got a almost completed app now and the next feature I want to implement is threading. I chose to go with BeginThread(), although am aware of TThread in delphi. The problem I'm coming across is the structure of BeginThread() call. Normally the line in the program that would call the function I want to be threaded is
CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);
op is a integer.
The line I've switched it out for to create a thread from it is
BeginThread(nil,0,CompareFiles,Addr('form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op'),0,x);
From the little amount of infromation I can find on how to actually use BeginThread() this should be a fine call, however on compling all I get is complier errors regarding the structure of my BeginThread() statement paramenters.
EDIT FOR INFORMATION.
The current procedure that calls CompareFiles is
procedure TForm1.Panel29Click(Sender: TObject);
var
op,x : integer;
begin
if (Form1.Edit3.Text <> '') AND (Form1.Edit4.Text <> '') then
begin
op := 3;
if RadioButton7.Checked = True then op := 0;
if RadioButton3.Checked = True then op := 1;
if RadioButton4.Checked = True then op := 2;
if RadioButton5.Checked = True then op := 3;
if RadioButton6.Checked = True then op := 4;
CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);
end;
end;
If I was to use TThread as suggested by a couple of people, and as displayed by Rob below, I'm confused at how a) I would pass op,Edit3/4.Text and StringGrid2 to the CompareFiles. Guessing from the example of TThread I've seen I thought I would replace the code above with TCompareFilesThread.Executeand the put the current code from Panel29Click into TCompareFilesThread.Create and then add
FEdit3Text := Edit3Text;
FEdit4Text := Edit4Text;
FGrid := Grid;
to this
FEdit3Text := Form1.Edit3.Text;
FEdit4Text := Form1.Edit4.Text;
FGrid := Form1.StringGrid2;
But I've got this nagging feeling that is totally off the mark.
That's not at all the way to use BeginThread. That function expects a pointer to a function that takes one parameter, but the function you're trying to call wants four. The one parameter you're giving to BeginThread for it to forward to the thread procedure is a string, but you evidently hope that some sort of magic will turn that string of characters into the values that those variables contain.
That's not how Delphi works, and even for the languages that can do something like that, it's generally discouraged to actually do it.
To pass multiple parameters to BeginThread, define a record with all the values you'll need, and also define a record pointer:
type
PCompareFilesParams = ^TCompareFilesParams;
TCompareFilesParams = record
Edit3Text,
Edit4Text: string;
Grid: TStringGrid;
Op: Integer;
end;
Change CompareFiles to accept a pointer to that record:
function CompareFiles(Params: PCompareFilesParams): Integer;
To start the thread, you'll need to allocate an instance of that record and populate its fields:
var
Params: PCompareFilesParams;
begin
New(Params);
Params.Edit3Text := Edit3.Text;
Params.Edit4Text := Edit4.Text;
Params.Grid := StringGrid2;
Params.Op := op;
BeginThread(nil, 0, #CompareFiles, Params, 0, x);
Implement CompareFiles like this so that the record will get freed before the thread terminates:
function CompareFiles(Params: PCompareFilesParams): Integer;
begin
try
// <Normal implementation goes here.>
finally
Dispose(Params);
end;
end;
You can make it all a lot easier if you just use TThread, though. You can make your descendant class have as many parameters as you want in its constructor, so you don't have to mess around with dynamically allocating and freeing a special record.
type
TCompareFilesThread = class(TThread)
private
FEdit3Text,
FEdit4Text: string;
FGrid: TStringGrid;
FOp: Integer;
procedure Execute; override;
public
constructor Create(const Edit3Text, Edit4Text: string; Grid: TStringGrid; Op: Integer);
property ReturnValue;
end;
constructor TCompareFilesThread.Create;
begin
inherited Create(False);
FEdit3Text := Edit3Text;
FEdit4Text := Edit4Text;
FGrid := Grid;
FOp := Op;
end;
procedure TCompareFilesThread.Execute;
begin
ReturnValue := CompareFiles(FEdit3Text, FEdit4Text, FGrid, FOp);
end;
Instead of calling BeginThread, you just instantiate the class and let it run:
var
ThreadRef: TThread;
ThreadRef := TCompareFilesThread.Create(Edit3.Text, Edit4.Text, StringGrid2, Op);
There's more to using threads, such as knowing when the thread has finished running, but I think you have enough to get started. One last thing to beware of, though, is that TStringGrid is a VCL control. You mustn't do anything with it from this new thread you create (regardless of how you end up creating it). Eveything you do with the grid control need to be done from the main thread. Use TThread.Synchronize and TThread.Queue to shift any VCL operations onto the main thread. Your file-comparing thread will wait for the synchronized operation to complete, but it will keep running without waiting for a queued operation to complete.

Resources