When i used this code with button, it is ok... All sockets connected success. All events working. no problem (OnSessionClosed, OnSessionConnected, ...)
procedure TfrmMain.btnConnectClick(Sender: TObject);
var
I: Integer;
begin
for I := 0 to query.RecordCount - 1 do
begin
pUser[I] := TUser.Create();
pUser[I].Connect(frmMain.editEbenezerIP.Text);
pUser[I].run := False;
pUser[I].username := Trim(query.FieldByName('strAccountID').Text);
pUser[I].password := Trim(query.FieldByName('strPasswd').Text);
pUser[I].md5 := editMD5.Text;
pUser[I].Resume;
query.Next;
end;
end;
i created a thread to connect with sleep. (my thread TConnector).
All thread connected but OnSessionConnected event not working when i created with TConnector.
No problem with button to use create sockets.
procedure TUser.OnSessionConnected(Sender: TObject; ErrCode: Word);
begin
ShowMessage('Connection success!');
end;
procedure TUser.Connect(eip : string);
begin
Initialize;
socket := TWSocket.Create(nil);
socket.OnDataAvailable := OnDataAvailable;
socket.OnSessionConnected := OnSessionConnected;
socket.OnSessionClosed := OnSessionClosed;
socket.Connect;
end;
procedure TConnector.Execute;
var
I : Integer;
pUser : array [0..1500] of TUser;
begin
for I := 0 to frmMain.query.RecordCount - 1 do
begin
pUser[I] := TUser.Create();
pUser[I].run := False;
pUser[I].username := Trim(frmMain.query.FieldByName('strAccountID').Text);
pUser[I].password := Trim(frmMain.query.FieldByName('strPasswd').Text);
pUser[I].Connect(frmMain.editEbenezerIP.Text);
pUser[I].Resume;
frmMain.query.Next;
**Sleep(100);**
end;
end;
I fixed this problem with Synchronize(CreateUser);. Thanks for your answers
TConnector = class(TThread)
private
protected
procedure Execute; override;
public
strAccountID, strPasswd, MD5, eIP : string;
X : Integer;
constructor Create;
procedure CreateUser;
end;
procedure TConnector.CreateUser;
begin
Output(Format('Thread for %s',[strAccountID]));
frmMain.pUser[X] := TUser.Create();
frmMain.pUser[X].run := False;
frmMain.pUser[X].username := strAccountID;
frmMain.pUser[X].password := strPasswd;
frmMain.pUser[X].md5 := MD5;
frmMain.pUser[X].Connect(eIP, frmMain);
frmMain.pUser[X].Resume;
end;
procedure TConnector.Execute;
var
I : Integer;
begin
MD5 := frmMain.editMD5.Text;
eIP := frmMain.editEbenezerIP.Text;
for I := 0 to frmMain.query.RecordCount - 1 do
begin
X := I;
strAccountID := Trim(frmMain.query.FieldByName('strAccountID').Text);
strPasswd := Trim(frmMain.query.FieldByName('strPasswd').Text);
**Synchronize(CreateUser);**
Sleep(1000);
frmMain.query.Next;
end;
while(not Terminated)do
begin
Sleep(1000);
OutPut('test');
end;
end;
TWSocket uses a non-blocking socket and a hidden window for handling socket state updates asynchronously. As such, you need to give your thread a message loop. It works in a TButton.OnClick event because it is utilizing the main thread's existing message loop.
Edit: The simplest message loop involves calling Peek/GetMessage(), TranslateMessage(), and DispatchMessage() in a loop for the lifetime of the thread, so you need to add those function calls to your worker thread, eg:
procedure TConnector.Execute;
var
I : Integer;
pUser : array [0..1500] of TUser;
Msg: TMsg
begin
for I := 0 to frmMain.query.RecordCount - 1 do
begin
if Terminated then Break;
pUser[I] := TUser.Create();
pUser[I].run := False;
pUser[I].username := Trim(frmMain.query.FieldByName('strAccountID').Text);
pUser[I].password := Trim(frmMain.query.FieldByName('strPasswd').Text);
pUser[I].Connect(frmMain.editEbenezerIP.Text);
pUser[I].Resume;
frmMain.query.Next;
end;
while (GetMessage(Msg, 0, 0, 0) > 0) and (not Terminated) then
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
// perform cleanup here as needed...
end;
procedure TConnector.Stop;
begin
Terminate;
PostThreadMessage(ThreadID, WM_QUIT, 0, 0);
end;
Related
I'm having trouble to receive a byte array containg a PNG file.
When the code is executed in OnClientRead event it works fine, already when transfered for a thread, happens an error of MemoryStream that says:
Out of memory while expanding memory stream.
At this point:
if SD.State = ReadingSize then
I want to know how to solve this specific trouble and also how can I check if I'm receiving a data that contains a file or a simple String?
The code:
type
TSock_Thread = class(TThread)
private
Socket: TCustomWinSocket;
public
constructor Create(aSocket: TCustomWinSocket);
procedure Execute; override;
end;
type
TInt32Bytes = record
case Integer of
0: (Bytes: array[0..SizeOf(Int32)-1] of Byte);
1: (Value: Int32);
end;
TSocketState = (ReadingSize, ReadingStream);
TSocketData = class
public
Stream: TMemoryStream;
Png: TPngImage;
State: TSocketState;
Size: TInt32Bytes;
Offset: Integer;
constructor Create;
destructor Destroy; override;
end;
{ ... }
constructor TSock_Thread.Create(aSocket: TCustomWinSocket);
begin
inherited Create(true);
Socket := aSocket;
FreeOnTerminate := true;
end;
procedure TSock_Thread.Execute;
var
s: String;
BytesReceived: Integer;
BufferPtr: PByte;
SD: TSocketData;
Item: TListItem;
begin
inherited;
while Socket.Connected do
begin
if Socket.ReceiveLength > 0 then
begin
s := Socket.ReceiveText;
{ SD := TSocketData(Socket.Data);
if SD.State = ReadingSize then
begin
while SD.Offset < SizeOf(Int32) do
begin
BytesReceived := Socket.ReceiveBuf(SD.Size.Bytes[SD.Offset],
SizeOf(Int32) - SD.Offset);
if BytesReceived <= 0 then
Exit;
Inc(SD.Offset, BytesReceived);
end;
SD.Size.Value := ntohl(SD.Size.Value);
SD.State := ReadingStream;
SD.Offset := 0;
SD.Stream.Size := SD.Size.Value;
end;
if SD.State = ReadingStream then
begin
if SD.Offset < SD.Size.Value then
begin
BufferPtr := PByte(SD.Stream.Memory);
Inc(BufferPtr, SD.Offset);
repeat
BytesReceived := Socket.ReceiveBuf(BufferPtr^,
SD.Size.Value - SD.Offset);
if BytesReceived <= 0 then
Exit;
Inc(BufferPtr, BytesReceived);
Inc(SD.Offset, BytesReceived);
until SD.Offset = SD.Size.Value;
end;
try
SD.Stream.Position := 0;
SD.Png.LoadFromStream(SD.Stream);
SD.Stream.Clear;
except
SD.Png.Assign(nil);
end;
Item := Form1.ListView1.Selected;
if (Item <> nil) and (Item.Data = Socket) then
Form1.img1.Picture.Graphic := SD.Png;
SD.State := ReadingSize;
SD.Offset := 0;
end; }
end;
Sleep(100);
end;
end;
procedure TForm1.ServerSocket1Accept(Sender: TObject; Socket: TCustomWinSocket);
var
TST: TSock_Thread;
begin
TST := TSock_Thread.Create(Socket);
TST.Resume;
end;
UPDATE:
The code in the answer is not working for me because ServerType=stThreadBlocking blocks all clients connections with the server. And because of this, I'm searching for something like this (ServerType=stNonBlocking, TThread and OnAccept event):
type
TSock_Thread = class(TThread)
private
Png: TPngImage;
Socket: TCustomWinSocket;
public
constructor Create(aSocket: TCustomWinSocket);
procedure Execute; override;
procedure PngReceived;
end;
// ...
// ===============================================================================
constructor TSock_Thread.Create(aSocket: TCustomWinSocket);
begin
inherited Create(true);
Socket := aSocket;
FreeOnTerminate := true;
end;
// ===============================================================================
procedure TSock_Thread.PngReceived;
var
Item: TListItem;
begin
Item := Form1.ListView1.Selected;
if (Item <> nil) and (Item.Data = Socket) then
Form1.img1.Picture.Graphic := Png;
end;
procedure TSock_Thread.Execute;
var
Reciving: Boolean;
DataSize: Integer;
Data: TMemoryStream;
s, sl: String;
begin
inherited;
while Socket.Connected do
begin
if Socket.ReceiveLength > 0 then
begin
s := Socket.ReceiveText;
if not Reciving then
begin
SetLength(sl, StrLen(PChar(s)) + 1);
StrLCopy(#sl[1], PChar(s), Length(sl) - 1);
DataSize := StrToInt(sl);
Data := TMemoryStream.Create;
Png := TPngImage.Create;
Delete(s, 1, Length(sl));
Reciving := true;
end;
try
Data.Write(s[1], Length(s));
if Data.Size = DataSize then
begin
Data.Position := 0;
Png.LoadFromStream(Data);
Synchronize(PngReceived);
Data.Free;
Reciving := false;
end;
except
Png.Assign(nil);
Png.Free;
Data.Free;
end;
end;
Sleep(100);
end;
end;
procedure TForm1.ServerSocket1Accept(Sender: TObject; Socket: TCustomWinSocket);
var
TST: TSock_Thread;
begin
TST := TSock_Thread.Create(Socket);
TST.Resume;
end;
This code has an error of conversion of data at this line:
DataSize := StrToInt(sl);
How can I fix this?
how solve this specific trouble
You are not using TServerSocket threading the way it is meant to be used.
If you want to use TServerSocket in stThreadBlocking mode (see my other answer for how to use TServerSocket in stNonBlocking mode), the correct way is to:
derive a thread class from TServerClientThread
override its virtual ClientExecute() method to do your I/O work (via TWinSocketStream)
use the TServerSocket.OnGetThread event to instantiate the thread.
If you don't do this, TServerSocket will create its own default threads (to fire the OnClient(Read|Write) events in the main thread), which will interfere with your manual threads.
Also, you don't need the state machine that I showed you in my answer to your other question. That was for event-driven code. Threaded I/O code can be written linearly instead.
Try something more like this:
type
TSock_Thread = class(TServerClientThread)
private
Png: TPngImage;
procedure PngReceived;
protected
procedure ClientExecute; override;
end;
type
TInt32Bytes = record
case Integer of
0: (Bytes: array[0..SizeOf(Int32)-1] of Byte);
1: (Value: Int32);
end;
procedure TSock_Thread.ClientExecute;
var
SocketStrm: TWinSocketStream;
Buffer: TMemoryStream;
Size: TInt32Bytes;
Offset: Integer;
BytesReceived: Integer;
BufferPtr: PByte;
begin
SocketStrm := TWinSocketStream.Create(ClientSocket, 5000);
try
Buffer := TMemoryStream.Create;
try
Png := TPngImage.Create;
try
while ClientSocket.Connected do
begin
if not SocketStrm.WaitForData(100) then Continue;
Offset := 0;
while Offset < SizeOf(Int32) do
begin
BytesReceived := SocketStrm.Read(Size.Bytes[Offset], SizeOf(Int32) - Offset);
if BytesReceived <= 0 then Exit;
Inc(Offset, BytesReceived);
end;
Size.Value := ntohl(Size.Value);
Buffer.Size := Size.Value;
BufferPtr := PByte(Buffer.Memory);
Offset := 0;
while Offset < Size.Value do
begin
BytesReceived := SocketStrm.Read(BufferPtr^, Size.Value - Offset);
if BytesReceived <= 0 then Exit;
Inc(BufferPtr, BytesReceived);
Inc(Offset, BytesReceived);
end;
Buffer.Position := 0;
try
Png.LoadFromStream(Buffer);
except
Png.Assign(nil);
end;
Synchronize(PngReceived);
end;
finally
Png.Free;
end;
finally
Buffer.Free;
end;
finally
SocketStrm.Free;
end;
end;
procedure TSock_Thread.PngReceived;
var
Item: TListItem;
begin
Item := Form1.ListView1.Selected;
if (Item <> nil) and (Item.Data = ClientSocket) then
Form1.img1.Picture.Graphic := Png;
end;
procedure TForm1.ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread);
begin
SocketThread := TSock_Thread.Create(False, ClientSocket);
end;
how i can check if i'm receiving a data that contains a file or a simple String?
The client needs to send that information to your server. You are already sending a value to specify the data size before sending the actual data. You should also preceed the data with a value to specify the data's type. Then you can handle the data according to its type as needed.
i am new to Threads, i have a List contains a strings. My goal is to make multiple threads do work to this List, this codes only for a single thread because i'm learning currently, however i get AV when i press start Button.
type
TDemoThread = class(TThread)
private
procedure Abort;
protected
procedure Execute; override;
public
List: TStringList;
end;
procedure TfrmMain.StartButton1Click(Sender: TObject);
var
i: integer;
List: Tstrings;
begin
for i := 0 to memo1.Lines.Count - 1 do
begin
List := TStringList.Create;
List.Add(memo1.Lines.Strings[i]);
end;
Thread := TDemoThread.Create(True);
Thread.FreeOnTerminate := True;
Thread.Start;
end;
procedure TDemoThread.Execute;
var
lHTTP: TIdHTTP;
i: integer;
X: Tstrings;
begin
inherited;
if Terminated then
Exit;
lHTTP := TIdHTTP.Create(nil);
X := TStringList.Create;
lHTTP.ReadTimeout := 30000;
lHTTP.HandleRedirects := True;
for i := 0 to List.Count - 1 do
try
X.Text := lHTTP.Get('https://instagram.com/' + List.Strings[i]);
S := ExtractDelimitedString(X.Text);
X.Clear;
TThread.Synchronize(nil,
procedure
begin
frmMain.Memo2.Lines.Add(List.Strings[i] + ' : ' + S);
end);
finally
end;
end;
Your problem is that you never assign to the List member of the thread class:
type
TDemoThread = class(TThread)
private
procedure Abort;
protected
procedure Execute; override;
public
List: TStringList; <-- never assigned to, hence always nil
end;
Hence the access violation.
It looks like you are trying to pass the contents of memo1 to the thread. I would do that like so:
type
TDemoThread = class(TThread)
private
FData: TStringList;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
end;
constructor TDemoThread.Create(Data: TStrings);
begin
inherited Create(False);
FData := TStringList.Create;
FData.Assign(Data);
FreeOnTerminate := True;
end;
destructor TDemoThread.Destroy;
begin
FData.Free;
inherited;
end;
procedure TDemoThread.Execute;
var
lHTTP: TIdHTTP;
i: integer;
X: TStrings;
begin
inherited;
if Terminated then
Exit;
lHTTP := TIdHTTP.Create(nil);
X := TStringList.Create;
lHTTP.ReadTimeout := 30000;
lHTTP.HandleRedirects := True;
for i := 0 to FData.Count - 1 do
try
X.Text := lHTTP.Get('https://instagram.com/' + FData[i]);
S := ExtractDelimitedString(X.Text);
X.Clear;
TThread.Synchronize(nil,
procedure
begin
frmMain.Memo2.Lines.Add(FData[i] + ' : ' + S);
end);
finally
end;
end;
procedure TfrmMain.StartButton1Click(Sender: TObject);
begin
TDemoThread.Create(memo1.Lines);
end;
It is pointless to create suspended and then immediately start. It is also not permitted to hold a reference to a FreeOnTerminate thread after it has started so I removed that.
The code in TDemoThread.Execute leaks, unless you are running exclusively on an ARC platform. And the try/finally is pointless. And you don't need a string list to hold a single string. Assuming you aren't using ARC it should be:
procedure TDemoThread.Execute;
var
lHTTP: TIdHTTP;
i: integer;
S: string;
begin
if Terminated then
Exit;
lHTTP := TIdHTTP.Create(nil);
try
lHTTP.ReadTimeout := 30000;
lHTTP.HandleRedirects := True;
for i := 0 to FData.Count - 1 do
begin
S := ExtractDelimitedString(lHTTP.Get('https://instagram.com/' + FData[i]));
TThread.Synchronize(nil,
procedure
begin
frmMain.Memo2.Lines.Add(FData[i] + ' : ' + S);
end);
end;
finally
lHTTP.Free;
end;
end;
Personally I'd avoid updating the form from the threads themselves. Threads are data generators here, not GUI managers. So let them separate their concerns.
I'd make all the threads accumulate the results into the same shared container and then make a GUI thread to poll that container instead. Human eyes are slow and Windows GUI is slow too, so you should not update your GUI more often than 2 or 3 times per second. It would only waste CPU load and blur the form into being unreadable.
Another thing would be to avoid using slow TStringList unless its extra functionality (which makes it slow) is required. The regular TList<string> is more than enough as a dumb container and is faster.
type
TDemoThread = class;
TfrmMain = class(TForm)
private
Fetchers: TThreadList<TDemoThread>;
Data: TThreadList<string>;
property inProcess: Boolean read ... write SetInProcess;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
....
end;
// this demo makes each thread per each line - that is actually a bad design
// one better use a thread pool working over the same queue and only have
// 20-40 worker threads for all the URLs
TDemoThread = class(TThread)
private
URL: string;
List: TThreadList<string>;
Tracker: TThreadList<TDemoThread>;
protected
procedure Execute; override;
end;
procedure TfrmMain.BeforeDestruction;
begin
while TThreadList.Count > 0 do
Sleep(100);
FreeAndNil( Fetchers );
Data.Free;
inherited;
end;
procedure TfrmMain.AfterConstruction;
begin
Fetchers := TThreadList<TDemoThread>.Create;
Data := TThreadList<string>.Create;
inherited;
end;
procedure TfrmMain.StartButton1Click(Sender: TObject);
var
i: integer;
List: Tstrings;
worker: TDemoThread;
URL: string;
begin
If inProcess then exit;
for URL in memo1.Lines do begin
worker := TDemoThread.Create(True);
worker.FreeOnTerminate := True;
worker.URL := URL;
worker.List := Data;
worker.Tracker := Fetchers;
Fetchers.Add( worker );
end;
InProcess := True;
for worker in Fetchers do
worker.Start;
end;
procedure TfrmMain.SetInProcess(const Value: Boolean);
begin
if Value = InProcess then exit; // form already is in this mode
FInProcess := Value;
memo1.ReadOnly := Value;
StartButton.Enabled := not Value;
if Value then begin
Memo2.Lines.Clear;
Data.Clear;
end;
Timer1.Delay := 500; // twice per second
Timer1.Enabled := Value;
If not Value then // for future optimisation - make immediate mode change
FlushData; // when last worker thread quits, no waiting for timer event
If not Value then
ShowMessage('Work complete');
end;
procedure TfrmMain.Timer1Timer(const Sender: TObject);
begin
FlushData;
if Fetchers.Count <= 0 then
InProcess := False;
end;
procedure TfrmMain.FlushData;
begin
Data.LockList; // next two operations should go as non-interruptible atom
try
Memo2.Lines.AddStrings( Data.ToArray() );
Data.Clear;
finally
Data.UnLockList;
end;
end;
procedure TDemoThread.Execute;
var
lHTTP: TIdHTTP;
begin
try
lHTTP := TIdHTTP.Create(nil);
try
lHTTP.ReadTimeout := 30000;
lHTTP.HandleRedirects := True;
S := ExtractDelimitedString( lHTTP.Get('https://instagram.com/' + URL) );
List.Add( S );
finally
lHTTP.Destroy;
end;
finally
Tracker.Remove( Self );
end;
end;
http://docwiki.embarcadero.com/Libraries/Seattle/en/System.Classes.TThreadList
http://docwiki.embarcadero.com/Libraries/Seattle/en/System.Classes.TStrings.AddStrings
PS. Personally, I'd also use OmniThreads Library, as it generally makes maintaining data-generating threads easier. For example just managing how many threads did you created becomes setting one property and determining when all threads complete their work is another oneliner. You really should not create a thousand of threads to fetch all the URLs, instead you should have 10-20 threads in a Thread Pool that would take the URLs from a Input Queue and fetch them one after another. I suggest you reading about OTL's Parallel For and Fork-Join patterns at http://otl.17slon.com/tutorials.htm - it would allow making such an application more concise and easier to write. Pipeline pattern would probably be even better match for this task - since you anyway prepare URLs list as a source collection. Half the scaffolding in StartButtonClick would be gone, and the whole TDemoThread class too.
Function
function DownloadString(AUrl: string): string;
var
LHttp: TIdHttp;
begin
LHttp := TIdHTTP.Create;
try
LHttp.HandleRedirects := true;
result := LHttp.Get('http://127.0.0.1/a.php?n='+AUrl);
finally
LHttp.Free;
end;
end;
Boot
procedure TForm1.Button1Click(Sender: TObject);
var
LUrlArray: TArray<String>;
begin
LUrlArray := form1.listbox1.Items.ToStringArray;
TThread.CreateAnonymousThread(
procedure
var
LResult: string;
LUrl: string;
begin
for LUrl in LUrlArray do
begin
LResult := DownloadString(LUrl);
TThread.Synchronize(nil,
procedure
begin
if Pos('DENEGADA',LResult)>0 then
begin
Memo1.Lines.Add(LResult);
end
else
begin
Memo1.Lines.Add(LResult + 'DIE');
end;
end
);
end;
end
).Start;
end;
Listbox Lines
http://127.0.0.1/a.php?n=4984
http://127.0.0.1/a.php?n=4986
http://127.0.0.1/a.php?n=4989
in this case only one thread will download all URL's content but I would like to make it creates a thread for each item...
example:
thread1 - check item1 listbox - http://127.0.0.1/a.php?n=4984
thread2 - check next item 4986
thread3 - check next item 4989
how make this? Is there any way to do this ?, I believe that this method will be more effective.
In order to create separate threads, you have to bind the url variable value like this:
procedure TForm1.Button1Click(Sender: TObject);
var
LUrlArray: TArray<String>;
LUrl: String;
function CaptureThreadTask(const s: String) : TProc;
begin
Result :=
procedure
var
LResult : String;
begin
LResult := DownloadString(s);
TThread.Synchronize(nil,
procedure
begin
if Pos('DENEGADA',LResult)>0 then
begin
Memo1.Lines.Add(LResult);
end
else
begin
Memo1.Lines.Add(LResult + 'DIE');
end;
end
);
end;
end;
begin
LUrlArray := form1.listbox1.Items.ToStringArray;
for LUrl in LUrlArray do
// Bind variable LUrl value like this
TThread.CreateAnonymousThread( CaptureThreadTask(LUrl)
).Start;
end;
See Anonymous Methods Variable Binding
You can try using ForEach pattern of omnithreadlibrary :
http://otl.17slon.com/book/chap04.html#highlevel-foreach
http://otl.17slon.com/book/chap04.html#leanpub-auto-iomniblockingcollection
Draft is like that:
TMyForm = class(TForm)
private
DownloadedStrings: iOmniBlockingCollection;
published
DownloadingProgress: TTimer;
MemoSourceURLs: TMemo;
MemoResults: TMemo;
...
published
procedure DownloadingProgressOnTimer( Sender: TObject );
procedure StartButtonClick ( Sender: TObject );
.....
private
property InDownloadProcess: boolean write SetInDownloadProcess;
procedure FlushCollectedData;
end;
procedure TMyForm.StartButtonClick ( Sender: TObject );
begin
DownloadedStrings := TOmniBlockingCollection.Create;
Parallel.ForEach<string>(MemoSourceURLs.Lines)
.NumTasks(10) // we do not want to overload computer by millions of threads when given a long list. We are not "fork bomb"
// .PreserveOrder - usually not a needed option
.Into(DownloadedStrings) // - or you would have to manually seal the container by calling .CompleteAdding AFTER the loop is over in .OnStop option
.NoWait
.Execute(
procedure (const URL: string; var res: TOmniValue)
var Data: string; Success: Boolean;
begin
if my_IsValidUrl(URL) then begin
Success := my_DownloadString( URL, Data);
if Success and my_IsValidData(Data) then begin
if ContainsText(Data, 'denegada') then
Data := Data + ' DIE';
res := Data;
end;
end
);
InDownloadProcess := true;
end;
procedure TMyForm.SetInDownloadProcess(const process: Boolean);
begin
if process then begin
StartButton.Hide;
Prohibit-Form-Closing := true;
MemoSourceURLs.ReadOnly := true;
MemoResults.Clear;
with DownloadingProgress do begin
Interval := 333; // update data in form 3 times per second - often enough
OnTimer := DownloadingProgressOnTimer;
Enabled := True;
end;
end else begin
DownloadingProgress.Enabled := false;
if nil <> DownloadedStrings then
FlushCollectedData; // one last time
Prohibit-Form-Closing := false;
MemoSourceURLs.ReadOnly := false;
StartButton.Show;
end;
end;
procedure TMyForm.FlushCollectedData;
var s: string; value: TOmniValue;
begin
while DownloadedStrings.TryTake(value) do begin
s := value;
MemoResults.Lines.Add(s);
end;
PostMessage( MemoResults.Handle, .... ); // not SendMessage, not Perform
// I do not remember, there was something very easy to make the memo auto-scroll to the last line added
end;
procedure TMyForm.DownloadingProgressOnTimer( Sender: TObject );
begin
if nil = DownloadedStrings then begin
InDownloadProcess := false;
exit;
end;
FlushCollectedData;
if DownloadedStrings.IsCompleted then begin
InDownloadProcess := false; // The ForEach loop is over, everything was downloaded
DownloadedStrings := nil; // free memory
end;
end;
http://docwiki.embarcadero.com/Libraries/XE4/en/System.StrUtils.ContainsText
http://docwiki.embarcadero.com/Libraries/Seattle/en/Vcl.ExtCtrls.TTimer_Properties
PS. note that the online version of the book is old, you perhaps would have to update it to features in the current version of the omnithreadlibrarysources.
PPS: your code has a subtle error:
for LUrl in LUrlArray do
begin
LResult := DownloadString(LUrl);
Given your implementation of DownloadString that means in the case of HTTP error your function would re-return the previous value of LResult again and again and again and.... until the no-error downloading happened.
That is why I changed your function definition to be clear when error happens and no output data is given.
UPDATE Problem Still Exists.
Is it possible to run code in already running thread? for example:
thread1 is running some code & i want to run code from thread2 in thread1.
I want to run code in idTCPServer thread to send some data to client
Edit:
After research seems that my problem is that when Client data is received or is receiving same time another thread is trying to write to that socket.
Edit:
procedure TMainFrm.UserSRVExecute(AContext: TIdContext);
var
Command : String;
msSize : Int64;
ms : TMemoryStream;
decompressedMS : TMemoryStream;
H : TIdNotify;
begin
// Application.ProcessMessages;
Command := AContext.Connection.Socket.ReadLn;
// messagebox(0,'snd','',$40);
if logb then mainfrm.mconnections.Lines.Add(command + ' - BEGIN');
if Command <> '' then // keepalive
begin
//Application.ProcessMessages;
msSize := AContext.Connection.Socket.ReadInt64;
ms := TMemoryStream.Create;
decompressedMS := TMemoryStream.Create;
try
AContext.Connection.Socket.ReadStream(ms, msSize);
ms.Position := 0;
DecompressStream(MS,decompressedMS);
decompressedMS.Position := 0;
Client_ProcessData(AContext,Command,decompressedMS);
finally
ms.Free;
decompressedMS.Free;
if logb then mainfrm.mconnections.Lines.Add(command + ' - END');
end;
end;
end;
procedure Client_ProcessData(AContext: TIdContext; cmd : String; data : TMemoryStream);
var
Hnd : THandle;
clData : TStringArray;
TmpStr1 : String;
Tmp : String;
TN : TIdNotify;
Sync : TMySync;
I,I2 : Integer;
begin
Hnd := AContext.Connection.Socket.Binding.Handle;
if cmd = 'scr' then // RECEIVE COMMAND TO SEND TO CLIENT TO RECEIVE DATA FROM CLIENT
begin
Tmp := StreamToString(data);
{Sync := TMySync2.Create(True);
try
Sync.cmd := cmd;
Sync.hnd := Hnd;
Sync.tmp := TmpStr1;
Sync.Resume;
finally
//Sync.Free;
end; }
log('>>> CLFROMAS: '+IntToStr(HND)+':::'+cmd+':::');
// SendCMDToSocket(MainFrm.UserSRV,StrToInt(Trim(Tmp)),'scr'+IntToStr(Hnd));
I2 := StrToInt(Trim(Tmp));
for I := 0 to 100 do
if USRVData[i].hnd = I2 then
begin
// cs.Acquire;
USRVData[i].CTX.Connection.Socket.WriteLn('scr'+IntToStr(Hnd)); // PLACED ALL CONTEXTs IN GLOBAL VARIABLE + ALL SOCKET HANDLES. <--- HERE IS THE PROBLEM
// cs.Release;
Break;
end;
// log('>>> CLFROMAS: '+IntToStr(HND)+':::'+cmd+':::'+streamtostring(data));
Exit;
end;
if Copy(cmd,1,Length('scr4u')) = 'scr4u' then // RECEIVE DATA FROM CLIENT TO SEND IT TO ADMIN CLIENT REQUEST ABOVE
begin
if Length(cmd) > Length('scr4u') then
begin
Delete(cmd,1,Length('scr4u'));
Data.Position := 0;
{ Sync := TMySync.Create;
try
Sync.cmd := cmd;
Sync.hnd := Hnd;
Sync.data := TMemoryStream.Create;
Sync.data.CopyFrom(data,data.Size);
Sync.data.Position := 0;
Sync.DoNotify;
finally
Sync.data.Free;
Sync.Free;
end; }
SendStreamToSocket(MainFrm.UserSRV,strtoint(cmd),'scr4u',Data);
log('>>>>> ADMIN: '+IntToStr(HND)+':::'+cmd+':::'{+streamtostring(data)});
end else TmpStr1 := '';
Exit;
end;
...
UPDATE
procedure TMainFrm.UserSRVExecute(AContext: TIdContext);
var
Command : String;
msSize : Int64;
ms : TMemoryStream;
decompressedMS : TMemoryStream;
H : TIdNotify;
I : Integer;
List, Messages : TStringList;
begin
Messages := nil;
try
List := TMyContext(AContext).OutgoingMessages.Lock;
try
if List.Count > 0 then
begin
Messages := TStringList.Create;
Messages.Assign(List);
List.Clear;
end;
finally
TMyContext(AContext).OutgoingMessages.Unlock;
end;
if Messages <> nil then
begin
for I := 0 to Messages.Count-1 do
begin
AContext.Connection.IOHandler.WriteLn(Messages.Strings[I]);
end;
end;
finally
Messages.Free;
end;
if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
AContext.Connection.IOHandler.CheckForDataOnSource(100);
AContext.Connection.IOHandler.CheckForDisconnect;
if AContext.Connection.IOHandler.InputBufferIsEmpty then
Exit;
end;
Command := AContext.Connection.Socket.ReadLn;
if logb then mainfrm.mconnections.Lines.Add(command + ' - BEGIN');
if Command <> '' then
begin
msSize := AContext.Connection.Socket.ReadInt64;
ms := TMemoryStream.Create;
decompressedMS := TMemoryStream.Create;
try
AContext.Connection.Socket.ReadStream(ms, msSize);
ms.Position := 0;
DecompressStream(MS,decompressedMS);
decompressedMS.Position := 0;
Client_ProcessData(AContext,Command,decompressedMS);
finally
ms.Free;
decompressedMS.Free;
if logb then mainfrm.mconnections.Lines.Add(command + ' - END');
end;
end;
end;
Is it possible to run code in already running thread? for example: thread1 is running some code & i want to run code from thread2 in thread1.
No. Thread1 needs to be explicitly coded to stop what it is currently doing, do something else, and then go back to what it was previous doing. All Thread2 can do is signal Thread1 to perform that stop+continue at its earliest convenience.
I want to run code in idTCPServer thread to send some data to client
Your TIdTCPServer.OnExecute event handler needs to check for that data periodically and send it when it is available.
You can use the TIdContext.Data property, or derive a custom class from TIdServerContext and assign it to the TIdTCPServer.ContextClass property, to provide a per-client thread-safe buffer for your outbound data. Your OnExecute handler can then access that buffer when needed.
For example:
type
TMyContext = class(TIdServerContext)
public
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
OutgoingMessages: TIdThreadSafeStringList;
end;
constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited;
OutgoingMessages := TIdThreadSafeStringList.Create;
end;
destructor TMyContext.Destroy;
begin
OutgoingMessages.Free;
inherited;
end;
procedure TMyForm.FormCreate(Sender: TObject);
begin
// this must be set before activating the server...
IdTCPServer1.ContextClass := TMyContext;
end;
procedure TMyForm.IdTCPServer1Execute(AContext: TIdContext);
var
List, Messages: TStringList;
begin
// check for outgoing data...
Messages := nil;
try
List := TMyContext(AContext).OutgoingMessages.LockList;
try
if List.Count > 0 then
begin
Messages := TStringList.Create;
Messages.Assign(List);
List.Clear;
end;
finally
TMyContext(AContext).OutgoingMessages.UnlockList;
end;
if Messages <> nil then
begin
// send Messages using AContext.Connection.IOHandler as needed...
end;
finally
Messages.Free;
end;
// check for incoming data...
if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
AContext.Connection.IOHandler.CheckForDataOnSource(100);
AContext.Connection.IOHandler.CheckForDisconnect;
if AContext.Connection.IOHandler.InputBufferIsEmpty then
Exit;
end;
// process incoming data as needed...
end;
procedure TForm1.SomeProcedure;
var
List: TIdContextList;
Context: TMyContext;
begin
List := IdTCPServer1.Contexts.LockList;
try
Context := TMyContext(List[SomeIndex]);
Context.OutgoingMessages.Add('something');
finally
IdTCPServer1.Contexts.UnlockList;
end;
end;
So, I've always faced MAJOR headaches when threading in delphi xe4-6, whether it be from threads not executing, exception handling causes app crashes, or simply the on terminate method never getting called. All the workarounds I've been instructed to use have become very tedious with issues still haunting me in XE6. My code generally has looked something like this:
procedure TmLoginForm.LoginClick(Sender: TObject);
var
l:TLoginThread;
begin
SyncTimer.Enabled:=true;
l:=TLoginThread.Create(true);
l.username:=UsernameEdit.Text;
l.password:=PasswordEdit.Text;
l.FreeOnTerminate:=true;
l.Start;
end;
procedure TLoginThread.Execute;
var
Success : Boolean;
Error : String;
begin
inherited;
Success := True;
if login(USERNAME,PASSWORD) then
begin
// do another network call maybe to get dif data.
end else
begin
Success := False;
Error := 'Login Failed. Check User/Pass combo.';
end;
Synchronize(
procedure
if success = true then
begin
DifferentForm.Show;
end else
begin
ShowMessage('Error: '+SLineBreak+Error);
end;
SyncTimer.Enabled := False;
end);
end;
And then I came across this unit from the samples in Delphi and from the forums:
unit AnonThread;
interface
uses
System.Classes, System.SysUtils, System.Generics.Collections;
type
EAnonymousThreadException = class(Exception);
TAnonymousThread<T> = class(TThread)
private
class var
CRunningThreads:TList<TThread>;
private
FThreadFunc: TFunc<T>;
FOnErrorProc: TProc<Exception>;
FOnFinishedProc: TProc<T>;
FResult: T;
FStartSuspended: Boolean;
private
procedure ThreadTerminate(Sender: TObject);
protected
procedure Execute; override;
public
constructor Create(AThreadFunc: TFunc<T>; AOnFinishedProc: TProc<T>;
AOnErrorProc: TProc<Exception>; ACreateSuspended: Boolean = False;
AFreeOnTerminate: Boolean = True);
class constructor Create;
class destructor Destroy;
end;
implementation
{$IFDEF MACOS}
uses
{$IFDEF IOS}
iOSapi.Foundation
{$ELSE}
MacApi.Foundation
{$ENDIF IOS}
;
{$ENDIF MACOS}
{ TAnonymousThread }
class constructor TAnonymousThread<T>.Create;
begin
inherited;
CRunningThreads := TList<TThread>.Create;
end;
class destructor TAnonymousThread<T>.Destroy;
begin
CRunningThreads.Free;
inherited;
end;
constructor TAnonymousThread<T>.Create(AThreadFunc: TFunc<T>; AOnFinishedProc: TProc<T>;
AOnErrorProc: TProc<Exception>; ACreateSuspended: Boolean = False; AFreeOnTerminate: Boolean = True);
begin
FOnFinishedProc := AOnFinishedProc;
FOnErrorProc := AOnErrorProc;
FThreadFunc := AThreadFunc;
OnTerminate := ThreadTerminate;
FreeOnTerminate := AFreeOnTerminate;
FStartSuspended := ACreateSuspended;
//Store a reference to this thread instance so it will play nicely in an ARC
//environment. Failure to do so can result in the TThread.Execute method
//not executing. See http://qc.embarcadero.com/wc/qcmain.aspx?d=113580
CRunningThreads.Add(Self);
inherited Create(ACreateSuspended);
end;
procedure TAnonymousThread<T>.Execute;
{$IFDEF MACOS}
var
lPool: NSAutoreleasePool;
{$ENDIF}
begin
{$IFDEF MACOS}
//Need to create an autorelease pool, otherwise any autorelease objects
//may leak.
//See https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-CJBFBEDI
lPool := TNSAutoreleasePool.Create;
try
{$ENDIF}
FResult := FThreadFunc;
{$IFDEF MACOS}
finally
lPool.drain;
end;
{$ENDIF}
end;
procedure TAnonymousThread<T>.ThreadTerminate(Sender: TObject);
var
lException: Exception;
begin
try
if Assigned(FatalException) and Assigned(FOnErrorProc) then
begin
if FatalException is Exception then
lException := Exception(FatalException)
else
lException := EAnonymousThreadException.Create(FatalException.ClassName);
FOnErrorProc(lException)
end
else if Assigned(FOnFinishedProc) then
FOnFinishedProc(FResult);
finally
CRunningThreads.Remove(Self);
end;
end;
end.
Why is that this anon thread unit above works flawlessly 100% of the time and my code crashes sometimes? For example, I can exec the same thread 6 times in a row, but then maybe on the 7th (or the first for that matter) time it causes the app to crash. No exceptions ever come up when debugging so I dont have a clue where to start fixing the issue. Also, why is it that I need a separate timer that calls "CheckSynchronize" for my code in order to GUI updates to happen but it is not needed when I use the anon thread unit?
Maybe someone can point me in the right direction to ask this question elsewhere if here is not the place. Sorry, I'm diving into documentation already, trying my best to understand.
Here is an example of a thread that may work 20 times in a row, but then randomly cause app to crash
inherited;
try
SQL:= 'Some SQL string';
if GetSQL(SQL,XMLData) then
synchronize(
procedure
var
i:Integer;
begin
try
mTasksForm.TasksListView.BeginUpdate;
if mTasksForm.TasksListView.Items.Count>0 then
mTasksForm.TasksListView.Items.Clear;
XMLDocument := TXMLDocument.Create(nil);
XMLDocument.Active:=True;
XMLDocument.Version:='1.0';
XMLDocument.LoadFromXML(XMLData);
XMLNode:=XMLDocument.DocumentElement.ChildNodes['Record'];
i:=0;
if XMLNode.ChildNodes['ID'].Text <>'' then
while XMLNode <> nil do
begin
LItem := mTasksForm.TasksListView.Items.AddItem;
with LItem do
begin
Text := XMLNode.ChildNodes['LOCATION'].Text;
Detail := XMLNode.ChildNodes['DESC'].Text +
SLineBreak+
'Assigned To: '+XMLNode.ChildNodes['NAME'].Text
tag := StrToInt(XMLNode.ChildNodes['ID'].Text);
color := TRectangle.Create(nil);
with color do
begin
if XMLNode.ChildNodes['STATUS'].Text = STATUS_DONE then
fill.Color := TAlphaColors.Lime
else if XMLNode.ChildNodes['STATUS'].Text = STATUS_OK then
fill.Color := TAlphaColors.Yellow
else
fill.Color := TAlphaColors.Crimson;
stroke.Color := fill.Color;
ButtonText := XMLNode.ChildNodes['STATUS'].Text;
end;
Bitmap := Color.MakeScreenshot;
end;
XMLNode:=XMLNode.NextSibling;
end;
finally
mTasksForm.TasksListView.EndUpdate;
for i := 0 to mTasksForm.TasksListView.Controls.Count-1 do
begin
if mTasksForm.TasksListView.Controls[I].ClassType = TSearchBox then
begin
SearchBox := TSearchBox(mTasksForm.TasksListView.Controls[I]);
Break;
end;
end;
SearchBox.Text:=' ';
SearchBox.text := ''; //have in here because if the searchbox has text, when attempting to add items then app crashes
end;
end)
else
error := 'Please check internet connection.';
finally
synchronize(
procedure
begin
if error <> '' then
ShowMessage('Erorr: '+error);
mTasksForm.Spinner.Visible:=false;
mTasksForm.SyncTimer.Enabled:=false;
end);
end;
end;
here is the GETSQL method
function GetSQL(SQL:String;var XMLData:String):Boolean;
var
PostResult,
ReturnCode : String;
PostData : TStringList;
IdHTTP : TIdHTTP;
XMLDocument : IXMLDocument;
XMLNode : IXMLNode;
Test : String;
begin
Result:=False;
XMLData:='';
XMLDocument:=TXMLDocument.Create(nil);
IdHTTP:=TIdHTTP.Create(nil);
PostData:=TStringList.Create;
PostData.Add('session='+SessionID);
PostData.Add('database='+Encode(DATABASE,''));
PostData.Add('sql='+Encode(SQL,''));
IdHTTP.Request.ContentEncoding:='UTF-8';
IdHTTP.Request.ContentType:='application/x-www-form-urlencoded';
IdHTTP.ConnectTimeout:=100000;
IdHTTP.ReadTimeout:=1000000;
try
PostResult:=IdHTTP.Post(SERVER_URL+GET_METHOD,PostData);
XMLDocument.Active:=True;
XMLDocument.Version:='1.0';
test := Decode(PostResult,'');
XMLDocument.LoadFromXML(Decode(PostResult,''));
XMLNode:=XMLDocument.DocumentElement;
try
ReturnCode:=XMLNode.ChildNodes['status'].Text;
except
ReturnCode:='200';
end;
if ReturnCode='' then begin
ReturnCode:='200';
end;
if ReturnCode='200' then begin
Result:=True;
XMLData:=Decode(PostResult,'');
end;
except
on E: Exception do begin
result:=false;
end;
end;
PostData.Free;
IdHTTP.Free;
end;