Following the tips from this Stack Overflow answer I created a simple application for Windows that can get a snapshot from the webcam, using DirectX library.
Now I am trying to get the same result using thread. Here is what I got so far:
TGetWebcam = class(TThread)
private
FWCVideo: TVideoImage;
FJpgShot: TJPEGImage;
procedure OnNewVideoFrame(Sender: TObject;
Width, Height: Integer; DataPtr: Pointer);
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
end;
constructor TGetWebcam.Create;
begin
FreeOnTerminate := True;
FJpgShot := TJPEGImage.Create;
FWCVideo := TVideoImage.Create;
FWCVideo.OnNewVideoFrame := OnNewVideoFrame;
inherited Create(False);
end;
destructor TGetWebcam.Destroy;
begin
FWCVideo.Free;
FJpgShot.Free;
inherited;
end;
procedure TGetWebcam.Execute;
var
TmpLst: TStringList;
JpgImg: TJpegImage;
begin
TmpLst := TStringList.Create;
try
FWCVideo.GetListOfDevices(TmpLst);
if TmpLst.Count <= 0 then Exit;
if FWCVideo.VideoStart(TmpLst[0]) = 0 then
begin
TmpLst.Clear;
FWCVideo.GetListOfSupportedVideoSizes(TmpLst);
if TmpLst.Count <= 0 then Exit;
FWCVideo.SetResolutionByIndex(TmpLst.Count - 1);
JpgImg := TJPEGImage.Create;
try
JpgImg.Assign(FJpgShot);
JpgImg.CompressionQuality := 50;
JpgImg.SaveToFile('c:\test.jpg');
finally
JpgImg.Free;
end;
FWCVideo.VideoStop;
end;
finally
TmpLst.Free;
end;
end;
procedure TGetWebcam.OnNewVideoFrame(Sender: TObject; Width, Height: Integer;
DataPtr: Pointer);
begin
FWCVideo.GetJPG(FJpgShot); // I added this procedure "GetJPG" to VFrames.pas
end;
Problem is, GetListOfDevices always return empty when using inside thread.
Please, what am I doing wrong? Thanks!
EDIT:
After many tests and debugging following Remy Lebeau great tips, my conclusion is that OnNewVideoFrame is never fired when using TVideoImage inside thread. So my next test was trying to get the webcam shot inside the same execute method that creates TVideoImage, after waiting for some seconds, and it worked in the first time, but next time it always get blank white images, I need to close the application and open again for it to work one more time. Here is a abstract of the code I am using:
procedure TGetWebcam.Execute;
var
WCVideo: TVideoImage;
TmpList: TStringList;
JpgShot: TJPEGImage;
begin
CoInitialize(nil);
try
WCVideo := TVideoImage.Create;
try
TmpList := TStringList.Create;
try
WCVideo.GetListOfDevices(TmpList);
if TmpList.Count = 0 then Exit;
if WCVideo.VideoStart(TmpList[0]) <> 0 then Exit;
TmpList.Clear;
WCVideo.GetListOfSupportedVideoSizes(TmpList);
if TmpList.Count = 0 then Exit;
WCVideo.SetResolutionByIndex(ScnResId);
Sleep(5000);
JpgShot := TJPEGImage.Create;
try
WCVideo.GetJPG(JpgShot);
JpgShot.SaveToFile('c:\test.jpg');
finally
JpgShot.Free;
end;
finally
WCVideo.VideoStop;
end;
finally
TmpList.Free;
end;
finally
WCVideo.Free;
end;
finally
CoUninitialize;
end;
end;
Please, why this code works in the first time it runs but in next times always get blank white images? Thanks!
DirectX uses ActiveX/COM interfaces. As such, your thread's Execute() method needs to initialize the COM library for itself via CoInitialize/Ex() before accessing any COM objects.
But more importantly, you are creating and using the TVideoImage object across thread boundaries. Most COM objects are not designed to be used across thread boundaries, they would have to be marshaled in order to do that. So don't use TVideoImage that way. Create, use, and destroy it all within the same thread (ie, inside your Execute() method).
Try this instead:
type
TGetWebcam = class(TThread)
private
FWCVideo: TVideoImage;
FJpgShot: TJPEGImage;
procedure OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
end;
...
uses
Winapi.ActiveX;
constructor TGetWebcam.Create;
begin
inherited Create(False);
FreeOnTerminate := True;
FJpgShot := TJPEGImage.Create;
end;
destructor TGetWebcam.Destroy;
begin
FJpgShot.Free;
inherited;
end;
procedure TGetWebcam.Execute;
var
TmpLst: TStringList;
JpgImg: TJpegImage;
begin
CoInitialize(nil);
try
FWCVideo := TVideoImage.Create;
try
FWCVideo.OnNewVideoFrame := OnNewVideoFrame;
TmpLst := TStringList.Create;
try
FWCVideo.GetListOfDevices(TmpLst);
if TmpLst.Count <= 0 then Exit;
if FWCVideo.VideoStart(TmpLst[0]) <> 0 then Exit;
try
TmpLst.Clear;
FWCVideo.GetListOfSupportedVideoSizes(TmpLst);
if TmpLst.Count <= 0 then Exit;
FWCVideo.SetResolutionByIndex(TmpLst.Count - 1);
JpgImg := TJPEGImage.Create;
try
JpgImg.Assign(FJpgShot);
JpgImg.CompressionQuality := 50;
JpgImg.SaveToFile('c:\test.jpg');
finally
JpgImg.Free;
end;
finally
FWCVideo.VideoStop;
end;
finally
TmpLst.Free;
end;
finally
FWCVideo.Free;
end;
finally
CoUninitialize;
end;
end;
procedure TGetWebcam.OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
begin
FWCVideo.GetJPG(FJpgShot);
end;
That being said, I would suggest a slightly tweaked approach - assuming the OnNewVideoFrame event is fired asynchronously, the thread should actually wait for the event to fire and not just assume it does, and also it should stop the video capture before using the captured JPG, eg:
uses
..., System.SyncObjs;
type
TGetWebcam = class(TThread)
private
FJpgShot: TJPEGImage;
FJpgShotReady: TEvent;
procedure OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
function GetJpgShot: Boolean;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
end;
...
uses
Winapi.ActiveX;
constructor TGetWebcam.Create;
begin
inherited Create(False);
FreeOnTerminate := True;
FJpgShot := TJPEGImage.Create;
FJpgShotReady := TEvent.Create;
end;
destructor TGetWebcam.Destroy;
begin
FJpgShot.Free;
FJpgShotReady.Free;
inherited;
end;
procedure TGetWebcam.Execute;
var
JpgImg: TJpegImage;
begin
CoInitialize(nil);
try
if not GetJpgShot() then Exit;
JpgImg := TJPEGImage.Create;
try
JpgImg.Assign(FJpgShot);
JpgImg.CompressionQuality := 50;
JpgImg.SaveToFile('c:\test.jpg');
finally
JpgImg.Free;
end;
finally
CoUninitialize;
end;
end;
function TGetWebcam.GetJpgShot: Boolean;
var
TmpLst: TStringList;
WCVideo: TVideoImage;
begin
Result := False;
WCVideo := TVideoImage.Create;
try
WCVideo.OnNewVideoFrame := OnNewVideoFrame;
TmpLst := TStringList.Create;
try
WCVideo.GetListOfDevices(TmpLst);
if TmpLst.Count < 1 then Exit;
if WCVideo.VideoStart(TmpLst[0]) <> 0 then Exit;
try
TmpLst.Clear;
WCVideo.GetListOfSupportedVideoSizes(TmpLst);
if TmpLst.Count < 1 then Exit;
WCVideo.SetResolutionByIndex(TmpLst.Count - 1);
Result := FJpgShotReady.WaitFor(5000) = wrSignaled;
finally
WCVideo.VideoStop;
end;
finally
TmpLst.Free;
end;
finally
WCVideo.Free;
end;
end;
procedure TGetWebcam.OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
begin
TVideoImage(Sender).GetJPG(FJpgShot);
FJpgShotReady.SetEvent;
end;
UPDATE: you might need to add a message loop to your thread in order for the OnNewVideoFrame event to fire correctly, eg:
uses
..., Winapi.Windows;
type
TGetWebcam = class(TThread)
private
FJpgShot: TJPEGImage;
FJpgShotReady: Boolean;
procedure OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
function GetJpgShot: Boolean;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
end;
...
uses
Winapi.ActiveX;
constructor TGetWebcam.Create;
begin
inherited Create(False);
FreeOnTerminate := True;
FJpgShot := TJPEGImage.Create;
end;
destructor TGetWebcam.Destroy;
begin
FJpgShot.Free;
inherited;
end;
procedure TGetWebcam.Execute;
var
JpgImg: TJpegImage;
begin
CoInitialize(nil);
try
if not GetJpgShot() then Exit;
JpgImg := TJPEGImage.Create;
try
JpgImg.Assign(FJpgShot);
JpgImg.CompressionQuality := 50;
JpgImg.SaveToFile('c:\test.jpg');
finally
JpgImg.Free;
end;
finally
CoUninitialize;
end;
end;
function TGetWebcam.GetJpgShot: Boolean;
var
TmpLst: TStringList;
WCVideo: TVideoImage;
Msg: TMSG;
begin
Result := False;
WCVideo := TVideoImage.Create;
try
WCVideo.OnNewVideoFrame := OnNewVideoFrame;
TmpLst := TStringList.Create;
try
WCVideo.GetListOfDevices(TmpLst);
if TmpLst.Count < 1 then Exit;
if WCVideo.VideoStart(TmpLst[0]) <> 0 then Exit;
try
TmpLst.Clear;
WCVideo.GetListOfSupportedVideoSizes(TmpLst);
if TmpLst.Count < 1 then Exit;
WCVideo.SetResolutionByIndex(TmpLst.Count - 1);
FJpgShotReady := False;
while (not FJpgShotReady) and GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
Result := FJpgShotReady;
finally
WCVideo.VideoStop;
end;
finally
TmpLst.Free;
end;
finally
WCVideo.Free;
end;
end;
procedure TGetWebcam.OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
begin
TVideoImage(Sender).GetJPG(FJpgShot);
FJpgShotReady := True;
end;
Related
I have a simple TidTCPServer Working on a console and accepting Data. My problem is when the client Send Stream but having a very high of speed exchange data, The server freeze after 70 lines and the CPU load of the server go to 70%; I don't know how can i resolve without adding a sleep between every send . below an example of Client and Server . Can you help me to resolve this (Server Side) thanks .
program Srv;
{$I Synopse.inc}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils, CustApp, Generics.Collections, IdTCPServer, IdCustomTCPServer, IdContext, IdGlobal, Db, mORMot, mORMotSQLite3, IdSync, functions, SynCommons, SynSQLite3Static;
type
{ TMyApplication }
TMyApplication = class(TCustomApplication)
var IdTCPServer: TIdTCPServer;
protected
procedure DoRun; override;
procedure ServerOnConnect(AContext: TIdContext);
procedure ServerOnExecute(AContext: TIdContext);
function ReceiveStream(AContext: TIdContext;Size:integer; var AStream: TStream);
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
end;
type
TLog = class(TIdNotify)
protected
FMsg: string;
procedure DoNotify; override;
public
class procedure LogMsg(const AMsg: string);
end;
{ TMyApplication }
procedure TLog.DoNotify;
var i:integer;
begin
writeln(FMsg);
end;
class procedure TLog.LogMsg(const AMsg: string);
begin
with TLog.Create do
try
FMsg := AMsg;
Notify;
except
Free;
raise;
end;
end;
function TMyApplication.ReceiveStream(AContext: TIdContext; var AStream: TStream)
: Boolean; overload;
var
LSize: LongInt;
begin
Result := True;
try
LSize := AContext.Connection.IOHandler.ReadLongInt();
AContext.Connection.IOHandler.ReadStream(AStream,LSize, False)
AStream.Seek(0,soFromBeginning);
except
Result := False;
end;
end;
procedure TMyApplication.ServerOnExecute(AContext: TIdContext);
var AStream:TMemoryStream;
begin
if (Acontext.Connection.IOHandler.InputBufferIsEmpty) then
begin
TLog.LogMsg('--: '+random(100000).ToString); //After executing Client this line is displayed 70 time and CPU load is from 40 % to 70%
AStream:=TMemoryStream.Create;
try
ReceiveStream(AContext,TStream(AStream));
// .. here we use AStream to execute some stuff
finally
Astream.free;
end;
end;
procedure TMyApplication.ServerOnConnect(AContext: TIdContext);
begin
TLog.LogMsg('connect');
end;
procedure TMyApplication.DoRun;
begin
IdTCPServer := tIdTCPServer.Create;
IdTCPServer.ListenQueue := 15;
IdTCPServer.MaxConnections := 0;
IdTCPServer.TerminateWaitTime := 5000;
with IdTCPServer.Bindings.Add
do begin
IP := '0.0.0.0';
Port := 80;
IPVersion:=Id_IPv4;
end;
IdTCPServer.OnConnect := ServerOnConnect;
IdTCPServer.OnDisconnect := ServerOnDiconnect;
IdTCPServer.OnExecute := ServerOnExecute;
IdTCPServer.Active := True;
while true do
begin
Classes.CheckSynchronize() ;
sleep(10);
end;
readln;
Terminate;
end;
constructor TMyApplication.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
StopOnException := True;
end;
destructor TMyApplication.Destroy;
begin
IdTCPServer.Free;
inherited Destroy;
end;
var
Application: TMyApplication;
begin
Application := TMyApplication.Create(nil);
Application.Title := 'My Application';
Application.Run;
Application.Free;
end.
Client
function TForm1.SendStream(AClient: TIdTCPClient; AStream: TStream): Boolean; overload;
var
StreamSize: LongInt;
begin
try
Result := True;
try
AStream.Seek(0,soFromBeginning);
StreamSize := (AStream.Size);
AClient.IOHandler.Write(LongInt(StreamSize));
AClient.IOHandler.WriteBufferOpen;
AClient.IOHandler.Write(AStream, 0, False);
AClient.IOHandler.WriteBufferFlush;
finally
AClient.IOHandler.WriteBufferClose;
end;
except
Result := False;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Packet:TPacket;
AStream:TMemoryStream;
begin
for i:=0 to 1000 do
begin
Application.ProcessMessages;
With Packet do
begin
MX := random(10000);
MY := random(10000);
end;
AStream:=TMemoryStream.Create;
try
AStream.Write(Packet,SizeOf(TPacket));
SendStream(IdTCPClientCmd,TStream(AStream));
finally
AStream.Free;
end;
end;
end;
On the server side, your InputBufferIsEmpty() check is backwards. If the client is sending a lot of data, InputBufferIsEmpty() is likely to become False eventually, which will cause your server code to enter a tight unyielding loop that doesn't actually read anything. Just get rid of the check entirely and let ReceiveStream() block until there is a packet available to read.
Also, why are you setting the server's ListenQueue to 15, but the MaxConnections to 0? MaxConnections=0 will force the server to immediately close every client connection that is accepted, so the OnExecute event will never get a chance to be called.
On the client side, there is no need to destroy and recreate the TMemoryStream on each loop iteration, you should reuse that object.
But more importantly, you are not using write buffering correctly, so either fix that or get rid of it. I would do the latter, as you are sending lots of small packets, so just let TCP's default coalescing handle the buffering for you.
And TIdIOHandler.Write(TStream)/TIdIOHandler.ReadStream() can exchange the stream size for you, you don't need to do that manually.
Try this instead:
Server
program Srv;
{$I Synopse.inc}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils, CustApp, Generics.Collections, IdTCPServer, IdCustomTCPServer, IdContext, IdGlobal, Db, mORMot, mORMotSQLite3, IdSync, functions, SynCommons, SynSQLite3Static;
type
{ TMyApplication }
TMyApplication = class(TCustomApplication)
var
IdTCPServer: TIdTCPServer;
protected
procedure DoRun; override;
procedure ServerOnConnect(AContext: TIdContext);
procedure ServerOnExecute(AContext: TIdContext);
function ReceiveStream(AContext: TIdContext; Size: Integer; var AStream: TStream);
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
end;
type
TLog = class(TIdNotify)
protected
FMsg: string;
procedure DoNotify; override;
public
class procedure LogMsg(const AMsg: string);
end;
{ TMyApplication }
procedure TLog.DoNotify;
begin
WriteLn(FMsg);
end;
class procedure TLog.LogMsg(const AMsg: string);
begin
with TLog.Create do
try
FMsg := AMsg;
Notify;
except
Free;
raise;
end;
end;
function TMyApplication.ReceiveStream(AContext: TIdContext; AStream: TStream): Boolean; overload;
begin
try
AContext.Connection.IOHandler.ReadStream(AStream, -1, False);
AStream.Position := 0;
Result := True;
except
Result := False;
end;
end;
procedure TMyApplication.ServerOnExecute(AContext: TIdContext);
var
AStream: TMemoryStream;
begin
AStream := TMemoryStream.Create;
try
if not ReceiveStream(AContext, AStream) then
begin
AContext.Connection.Disconnect;
Exit;
end;
TLog.LogMsg('--: '+random(100000).ToString); //After executing Client this line is displayed 70 time and CPU load is from 40 % to 70%
// .. here we use AStream to execute some stuff
finally
AStream.Free;
end;
end;
procedure TMyApplication.ServerOnConnect(AContext: TIdContext);
begin
TLog.LogMsg('connect');
AContext.Connection.IOHandler.LargeStream := False;
end;
procedure TMyApplication.DoRun;
begin
IdTCPServer := TIdTCPServer.Create;
IdTCPServer.ListenQueue := 15;
IdTCPServer.MaxConnections := 1;
IdTCPServer.TerminateWaitTime := 5000;
with IdTCPServer.Bindings.Add do
begin
IP := '0.0.0.0';
Port := 80;
IPVersion := Id_IPv4;
end;
IdTCPServer.OnConnect := ServerOnConnect;
IdTCPServer.OnDisconnect := ServerOnDiconnect;
IdTCPServer.OnExecute := ServerOnExecute;
IdTCPServer.Active := True;
while True do
begin
Classes.CheckSynchronize();
Sleep(10);
end;
ReadLn;
Terminate;
end;
constructor TMyApplication.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
StopOnException := True;
end;
destructor TMyApplication.Destroy;
begin
IdTCPServer.Free;
inherited Destroy;
end;
var
Application: TMyApplication;
begin
Application := TMyApplication.Create(nil);
Application.Title := 'My Application';
Application.Run;
Application.Free;
end.
Client
function TForm1.SendStream(AClient: TIdTCPClient; AStream: TStream): Boolean; overload;
begin
try
AClient.IOHandler.LargeStream := False; // <-- or, set this 1 time after TIdTCPClient.Connect() exits...
AClient.IOHandler.Write(AStream, 0, True);
Result := True;
except
Result := False;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Packet: TPacket;
AStream: TMemoryStream;
i: Integer;
begin
AStream := TMemoryStream.Create;
try
AStream.Size := SizeOf(TPacket);
for i := 0 to 1000 do
begin
Application.ProcessMessages;
with Packet do
begin
MX := random(10000);
MY := random(10000);
end;
AStream.Position := 0;
AStream.Write(Packet, SizeOf(TPacket));
SendStream(IdTCPClientCmd, AStream);
end;
finally
AStream.Free;
end;
end;
After some time my client thread stops receiving/sending commands from/to TIdTcpServer.
Here is the client side thread I copied from an example from Remy:
Tested locally and it doesn't happen, only on a running environment the error occurs...
type
TDataEvent = procedure(const LBuffer: TIdBytes) of object;
TReadingThread = class(TThread)
private
FClient : TIdTCPClient;
FData : TIdBytes;
FOnData : TDataEvent;
procedure DataReceived;
protected
procedure Execute; override;
public
constructor Create(AClient: TIdTCPClient); reintroduce;
property OnData: TDataEvent read FOnData write FOnData;
end;
constructor TReadingThread.Create(AClient: TIdTCPClient);
begin
inherited Create(True);
FClient := AClient;
end;
procedure TReadingThread.Execute;
begin
while not Terminated do
begin
Form1.Cliente.IOHandler.ReadBytes(FData, szProtocol, False);
if (FData <> nil) and Assigned(FOnData) then Synchronize(DataReceived);
end;
end;
procedure TReadingThread.DataReceived;
begin
if Assigned(FOnData) then FOnData(FData);
end;
procedure TForm1.DataReceived(const LBuffer: TIdBytes);
type
PTBytes = ^TBytes;
PTIdBytes = ^TIdBytes;
var
LDataSize : Integer;
LProtocol : TProtocol;
LBuffer2 : TBytes;
LProtocol2 : TProtocol;
begin
LProtocol := BytesToProtocol(PTBytes(#LBuffer)^);
case LProtocol.Command of
cmdHWID:
begin
HWID := LProtocol.Sender.HWID;
end;
cmdPing:
begin
InitProtocol(LProtocol2);
LProtocol2.Command := cmdPing;
LProtocol2.Sender.PBack := GetTickCount;
LBuffer2 := ProtocolToBytes(LProtocol2);
Form1.Cliente.IOHandler.Write(PTIdBytes(#LBuffer2)^);
ClearBuffer(LBuffer2);
end;
end;
end;
For a while all works perfectly, but after some time, the client side stops receiving/sending. The connection to the server is seems to be still open.
function to find connection by ip:
list := IdTCPServer1.Contexts.LockList;
try
for i := 0 to list.Count - 1 do
begin
ctx := TIdContext(list[i]);
if ctx.Binding.PeerIP = Edit9.Text then
begin
TLog.AddMsg('IP FOUND');
Achou := True;
Cliente := TClientContext(ctx);
SerialCn := Cliente.Client.HWID;
IpCn := Cliente.Client.IP;
break;
end;
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
Can someone can explain me why i receive sometime an Exception "Argument out of range" under the ios simulator when i execute the code below? on android i never get any error. I use Delphi berlin.
the functions where the error appear :
{**********************************************************************}
procedure Twin_WorkerThreadPool.Enqueue(const Value: Twin_WorkerThread);
begin
Tmonitor.Enter(fPool);
try
fPool.Add(Value);
fSignal.SetEvent;
finally
Tmonitor.Exit(fPool);
end;
end;
{********************************************************}
function Twin_WorkerThreadPool.Dequeue: Twin_WorkerThread;
begin
Tmonitor.Enter(self); // << only one thread can execute the code below
try
Tmonitor.Enter(fPool);
try
if Fpool.Count > 0 then begin
result := fPool[Fpool.Count - 1];
fPool.Delete(Fpool.Count - 1);
exit;
end;
fSignal.ResetEvent;
finally
Tmonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
Tmonitor.Enter(fPool);
try
result := fPool[Fpool.Count - 1]; // << exception argument out of range ? but how it's possible ?
fPool.Delete(Fpool.Count - 1);
finally
Tmonitor.Exit(fPool);
end;
finally
Tmonitor.exit(self);
end;
end;
Below the full source code :
{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
Twin_WorkerThreadPool = class(TObject)
private
fPool: TObjectList<Twin_WorkerThread>;
fSignal: Tevent;
public
procedure Enqueue(const Value: Twin_WorkerThread);
function Dequeue: Twin_WorkerThread;
end;
{***********************************}
constructor Twin_WorkerThread.Create;
begin
FProc := nil;
FProcReadySignal := TEvent.Create(nil, false{ManualReset}, false, '');
FProcFinishedSignal := TEvent.Create(nil, false{ManualReset}, false, '');
inherited Create(False); // see http://www.gerixsoft.com/blog/delphi/fixing-symbol-resume-deprecated-warning-delphi-2010
end;
{***********************************}
destructor Twin_WorkerThread.Destroy;
begin
Terminate;
FProcReadySignal.setevent;
WaitFor;
FProcReadySignal.Free;
FProcFinishedSignal.Free;
inherited;
end;
{**********************************}
procedure Twin_WorkerThread.Execute;
begin
while True do begin
try
//wait the signal
FProcReadySignal.WaitFor(INFINITE);
//if terminated then exit
if Terminated then Break;
//execute fProc
if assigned(FProc) then FProc();
//signal the proc is finished
FProcFinishedSignal.SetEvent;
except
//hide the exception
end;
end;
end;
{**********************************************************}
procedure Twin_WorkerThread.ExecuteProc(const AProc: TProc);
begin
fProc := AProc;
FProcFinishedSignal.ResetEvent;
FProcReadySignal.SetEvent;
end;
{*****************************************************************}
procedure Twin_WorkerThread.ExecuteAndWaitProc(const AProc: TProc);
begin
fProc := AProc;
FProcFinishedSignal.ResetEvent;
FProcReadySignal.SetEvent;
FProcFinishedSignal.WaitFor(INFINITE);
end;
{********************************************************************}
constructor Twin_WorkerThreadPool.Create(const aThreadCount: integer);
var i: integer;
begin
fPool := TObjectList<Twin_WorkerThread>.create(false{aOwnObjects});
fSignal := TEvent.Create(nil, false{ManualReset}, false, '');
for I := 0 to aThreadCount - 1 do
fPool.Add(Twin_WorkerThread.Create)
end;
{***************************************}
destructor Twin_WorkerThreadPool.Destroy;
var i: integer;
begin
for I := 0 to fPool.Count - 1 do begin
fPool[i].disposeOf;
fPool[i] := nil;
end;
fPool.Free;
fSignal.Free;
inherited Destroy;
end;
{*********************************************************************}
procedure Twin_WorkerThreadPool.ExecuteAndWaitProc(const AProc: TProc);
var aThread: Twin_WorkerThread;
begin
aThread := Dequeue;
try
aThread.ExecuteAndWaitProc(aProc);
finally
Enqueue(aThread);
end;
end;
NOTE:
Just to explain a little better, remember it's only on ios it's not work, and if i add a sleep(1000) after the fSignal.resetEvent then it's work :
Tmonitor.Enter(fPool);
try
if Fpool.Count > 0 then begin
result := fPool[Fpool.Count - 1];
fPool.Delete(Fpool.Count - 1);
exit;
end;
fSignal.ResetEvent;
sleep(1000);
finally
Tmonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
so it's look like that the signal is not set to OFF just after doing fSignal.ResetEvent;
i m affraid it's a bug in TEvent or Tmonitor :(
Your pool is using an auto-reset event when it should be using a manual-reset event instead. You don't want each wait operation in Dequeue() to reset the event while there are still threads in the pool. It should be signaled while any item is in the pool, and unsignaled while the pool is empty. And your constructor is not signaling the event after adding your initial threads to the pool so they can be dequeued layer.
As for Dequeue() itself, is a bit more complicated than it should be. It can be simplified to something more like the following instead:
procedure Twin_WorkerThreadPool.Enqueue(const Value: Twin_WorkerThread);
begin
TMonitor.Enter(fPool);
try
fPool.Add(Value);
if fPool.Count = 1 then
fSignal.SetEvent;
finally
TMonitor.Exit(fPool);
end;
end;
function Twin_WorkerThreadPool.Dequeue: Twin_WorkerThread;
begin
repeat
TMonitor.Enter(fPool);
try
if fPool.Count > 0 then begin
Result := fPool[fPool.Count - 1];
fPool.Delete(fPool.Count - 1);
if fPool.Count = 0 then
fSignal.ResetEvent;
Exit;
end;
finally
TMonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
until False;
end;
Something else I notice is that fPool is a TObjectList<T> with its OwnsObjects property set to false, which kind of defeats the purpose of using TObjectList. You may as well just use TList<T> instead. And actually, the way you are using fPool, you should use TStack<T> instead. It would clean up your code a little more and make it that much easier to read and understand.
Try this instead:
type
Twin_WorkerThreadPool = class(TObject)
private
fPool: TStack<Twin_WorkerThread>;
fSignal: Tevent;
public
constructor Create(const aThreadCount: integer);
destructor Destroy; override;
procedure Enqueue(const Value: Twin_WorkerThread);
function Dequeue: Twin_WorkerThread;
end;
constructor Twin_WorkerThreadPool.Create(const aThreadCount: integer);
var
i: integer;
begin
inherited Create;
fPool := TStack<Twin_WorkerThread>.Create;
fSignal := TEvent.Create(nil, True{ManualReset}, False, '');
for I := 0 to aThreadCount - 1 do
fPool.Add(Twin_WorkerThread.Create);
if fPool.Count > 0 then
fPool.SetEvent;
end;
destructor Twin_WorkerThreadPool.Destroy;
var
i: integer;
begin
for I := fPool.Count - 1 downto 0 do
fPool.Pop.DisposeOf;
fPool.Free;
fSignal.Free;
inherited Destroy;
end;
procedure Twin_WorkerThreadPool.ExecuteAndWaitProc(const AProc: TProc);
var
aThread: Twin_WorkerThread;
begin
aThread := Dequeue;
try
aThread.ExecuteAndWaitProc(aProc);
finally
Enqueue(aThread);
end;
end;
procedure Twin_WorkerThreadPool.Enqueue(const Value: Twin_WorkerThread);
begin
TMonitor.Enter(fPool);
try
fPool.Push(Value);
if fPool.Count = 1 then
fSignal.SetEvent;
finally
TMonitor.Exit(fPool);
end;
end;
function Twin_WorkerThreadPool.Dequeue: Twin_WorkerThread;
begin
repeat
TMonitor.Enter(fPool);
try
if fPool.Count > 0 then begin
Result := fPool.Pop;
if fPool.Count = 0 then
fSignal.ResetEvent;
Exit;
end;
finally
TMonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
until False;
end;
I would like to implement a simple watchdog timer in Delphi XE 7 with two use cases:
• Watchdog ensures that a operation doesn't execute longer than x seconds
• Watchdog ensures that when errors occur then message exception will be stored in log file
Could you please suggest me any solution?
Here is my solution. I'm not sure that is a proper, but its works. I crated a new thread:
type
// will store all running processes
TProcessRecord = record
Handle: THandle;
DateTimeBegin, DateTimeTerminate: TDateTime;
end;
TWatchDogTimerThread = class(TThread)
private
FItems: TList<TProcessRecord>;
FItemsCS: TCriticalSection;
class var FInstance: TWatchDogTimerThread;
function IsProcessRunning(const AItem: TProcessRecord): Boolean;
function IsProcessTimedOut(const AItem: TProcessRecord): Boolean;
procedure InternalKillProcess(const AItem: TProcessRecord);
protected
constructor Create;
procedure Execute; override;
public
class function Instance: TWatchDogTimerThread;
destructor Destroy; override;
procedure AddItem(AProcess: THandle; ADateStart: TDateTime; ATimeOutMS: Cardinal);
end;
const
csPocessThreadLatencyTimeMs = 500;
And here is an implementation part:
procedure TWatchDogTimerThread.Execute;
var
i: Integer;
begin
while not Terminated do
begin
Sleep(csPocessThreadLatencyTimeMs);
FItemsCS.Enter;
try
i := 0;
while i < FItems.Count do
begin
if not IsProcessRunning(FItems[i]) then
begin
FItems.Delete(i);
end
else if IsProcessTimedOut(FItems[i]) then
begin
InternalKillProcess(FItems[i]);
FItems.Delete(i);
end
else
Inc(i);
end;
finally
FItemsCS.Leave;
end;
end;
end;
procedure TWatchDogTimerThread.AddItem(AProcess: THandle; ADateStart: TDateTime; ATimeOutMS: Cardinal);
var
LItem: TProcessRecord;
begin
LItem.Handle := AProcess;
LItem.DateTimeBegin := ADateStart;
LItem.DateTimeTerminate := IncMilliSecond(ADateStart, ATimeOutMS);
FItemsCS.Enter;
try
FItems.Add(LItem);
finally
FItemsCS.Leave;
end;
end;
constructor TWatchDogTimerThread.Create;
begin
inherited Create(False);
FItems := TList<TProcessRecord>.Create;
FItemsCS := TCriticalSection.Create;
end;
destructor TWatchDogTimerThread.Destroy;
begin
FreeAndNil(FItemsCS);
FItems.Free;
FInstance := nil;
inherited;
end;
class function TWatchDogTimerThread.Instance: TWatchDogTimerThread;
begin
if not Assigned(FInstance) then
FInstance := Create;
Result := FInstance;
end;
procedure TWatchDogTimerThread.InternalKillProcess(const AItem: TProcessRecord);
begin
if AItem.Handle <> 0 then
TerminateProcess(AItem.Handle, 0);
end;
function TWatchDogTimerThread.IsProcessRunning(const AItem: TProcessRecord): Boolean;
var
LPID: DWORD;
begin
LPID := 0;
if AItem.Handle <> 0 then
GetWindowThreadProcessId(AItem.Handle, #LPID);
Result := LPID <> 0;
end;
function TWatchDogTimerThread.IsProcessTimedOut(const AItem: TProcessRecord): Boolean;
begin
Result := (AItem.DateTimeTerminate < Now);// and IsProcessRunning(AItem);
end;
end.
I'm using Delphi7 and VFrames (TVideoImage) with this Procedure
uses VFrames;
....
procedure TForm1.snapshot;
var
cam:TVideoImage;
strlst:TStringList;
BMP:TBitmap;
begin
strlst := TStringList.Create ;
cam :=TVideoImage.Create;
cam.GetListOfDevices(strlst);
cam.VideoStart(strlst.Strings[0]); //specify a cam by number
//get snapshot
BMP := TBitmap.Create;
cam.GetBitmap(BMP);
BMP.SaveToFile('test.bmp');
cam.VideoStop;
BMP.Free;
end;
Result blank Bitmap file.
I made a small wrapper class for VFrames/VSample:
unit u_class_webcam;
interface
uses
Jpeg,
Forms,
VSample,
VFrames,
Classes,
Graphics,
SysUtils;
type
TWebcam = class
private
Video : TVideoImage;
Devices : TStringList;
Resolutions : TStringList;
function GetDeviceReady: Boolean;
function GetHeight: Integer;
function GetWidth: Integer;
function GetActiveDevice: String;
public
constructor Create;
destructor Destroy; override;
procedure SetDisplayCanvas(const Canvas : TCanvas);
procedure TakeSnapshot(const Filename : String);
function TakeSnapshotToBmp : TBitmap;
procedure Start;
procedure Stop;
property DeviceReady : Boolean read GetDeviceReady;
property Width : Integer read GetWidth;
property Height : Integer read GetHeight;
property ActiveDevice : String read GetActiveDevice;
end;
// webcam singleton
var
Webcam : TWebcam;
implementation
{ TWebcam }
function TWebcam.GetActiveDevice: String;
begin
Result := '';
if Devices.Count > 0 then
Result := Devices[0];
end;
function TWebcam.GetHeight: Integer;
begin
Result := Video.VideoHeight;
end;
function TWebcam.GetWidth: Integer;
begin
Result := Video.VideoWidth;
end;
function TWebcam.GetDeviceReady: Boolean;
begin
Video.GetListOfDevices(Devices);
Result := Devices.Count > 0;
end;
procedure TWebcam.SetDisplayCanvas(const Canvas : TCanvas);
begin
Video.SetDisplayCanvas(Canvas);
end;
function TWebcam.TakeSnapshotToBmp : TBitmap;
begin
Result := TBitmap.Create;
Bitmap.PixelFormat := pf24bit;
Video.GetBitmap(Result);
end;
procedure TWebcam.TakeSnapshot(const Filename: String);
var
Bitmap : TBitmap;
Jpeg : TJpegImage;
begin
Bitmap := TBitmap.Create;
JPeg := TJpegImage.Create;
try
Bitmap.PixelFormat := pf24bit;
Video.GetBitmap(Bitmap);
JPeg.Assign(Bitmap);
JPeg.SaveToFile(Filename);
finally
Bitmap.Free;
JPeg.Free;
end;
end;
procedure TWebcam.Start;
begin
if DeviceReady then
begin
Video.VideoStart(Devices[0]);
Video.GetListOfSupportedVideoSizes(Resolutions);
Video.SetResolutionByIndex(Resolutions.Count-1);
end;
end;
procedure TWebcam.Stop;
begin
if Video.VideoRunning then
Video.VideoStop;
end;
constructor TWebcam.Create;
begin
Devices := TStringList.Create;
Resolutions := TStringList.Create;
Video := TVideoImage.Create;
end;
destructor TWebcam.Destroy;
begin
Stop;
Devices.Free;
Resolutions.Free;
Application.ProcessMessages;
Video.Free;
end;
end.
usage:
procedure TForm1.TestIt;
var Bmp : TBitmap;
begin
WebCam := TWebCam.Create;
try
WebCam.Start;
WebCam.SetDisplayCanvas(Self.Canvas);
Bmp := WebCam.TakeSnapShotToBmp;
// do something with BMP
Bmp.Free;
WebCam.Stop;
finally
WebCam.Free;
end;
end;
Since the GetBitmap Function of TVideoImage may deliver empty images if directly called after the call to VideoStart, it might be necessary to Create TVideoImage add an OnNewVideoFrame event to get the information that an image is available. So the steps would be:
Create and start
wait for an image an take it
Free
Since the question was asking for a single shot solution and threading or idle looping after VideoStart do not work, I'd provide a solutions which would encapsulate the mentioned steps.
The call would be:
procedure TMyForm.FormCreate(Sender: TObject);
begin
ReportMemoryLeaksOnShutDown := true;
end;
procedure TMyForm.ImgCallBack(BMP:TBitMap);
begin
Image1.Picture.Assign(BMP);
end;
procedure TMyForm.Button3Click(Sender: TObject);
begin
With TGrabClass.Create do GetImage(ImgCallBack);
end;
with the base implementation of TGrabClass of:
unit u_GrabOnlyBitMap;
interface
uses
Classes,
Messages,
Windows,
Graphics,
VSample,
VFrames;
type
TImageCallBack=Procedure(bmp:TBitMap) of Object;
TGrabClass=Class
FReady:Boolean;
FVideo:TVideoImage;
FBitMap:TBitMap;
Handle:THandle;
FImageCallBack:TImageCallBack;
Procedure GetImage(cb:TImageCallBack);
Constructor Create;
Destructor Destroy;Override;
private
procedure NewVideoFrameEvent(Sender: TObject; Width, Height: integer;
DataPtr: pointer);
procedure WndMethod(var Msg: TMessage);
procedure Suicide;
End;
implementation
const
WM_MyKill=WM_user + 666;
// Called by asnc PostMessage with WM_MyKill to free
Procedure TGrabClass.WndMethod(var Msg: TMessage);
begin
if Msg.Msg = WM_MyKill then
begin
Msg.Result := -1;
Free;
end
else
Msg.Result := DefWindowProc(Handle, Msg.Msg, Msg.wParam, Msg.lParam);
end;
constructor TGrabClass.Create;
var
sl:TStringList;
begin
inherited;
Handle := AllocateHWnd(WndMethod);
sl:=TStringList.Create;
FVideo:=TVideoImage.Create;
FBitMap := TBitmap.Create;
FVideo.OnNewVideoFrame := NewVideoFrameEvent;
FVideo.GetListOfDevices(sl);
FReady := sl.Count > 0;
if FReady then FVideo.VideoStart(sl[0])
else Suicide;
sl.Free;
end;
destructor TGrabClass.Destroy;
begin
DeallocateHWnd(Handle);
FVideo.VideoStop;
FVideo.Free;
FBitMap.Free;
inherited;
end;
Procedure TGrabClass.Suicide;
begin
// No device found Callback with empty image and Postmessage for freeing
if Assigned(FImageCallBack) then FImageCallBack(FBitMap);
PostMessage(handle,WM_MyKill,0,0);
end;
Procedure TGrabClass.NewVideoFrameEvent(Sender : TObject; Width, Height: integer; DataPtr: pointer);
begin // we got a bitmap
FVideo.OnNewVideoFrame := Nil;
FVideo.GetBitmap(FBitMap);
if Assigned(FImageCallBack) then FImageCallBack(FBitMap);
PostMessage(handle,WM_MyKill,0,0);
end;
procedure TGrabClass.GetImage(cb: TImageCallBack);
begin
FImageCallBack := cb;
end;
end.