How to terminate a parallel.foreach from OTL in Delphi xe2 - delphi

I am learning how to use OmniThreadLibrary in Delphi XE2, i wonder if someone can show me how to cancel a parallel.foreach.
I read that I should use a cancellation token, but I cannot find an example of some sort on how to use it.
This is the original for loop inside the function.
function SomeFunction() : string;
begin
for value := 0 to length(listOfThings)-1 do
begin
Chain := Function1( listOfThings[value] );
if Evaluate( Chain , Solution) then
Parameters[value] := Solution
else
begin
Result := 'ERROR';
exit;
end;
end;
end;
And this is how I am using the Parallel.ForEach
function SomeFunction() : string;
begin
Parallel.ForEach(0, length(listOfThings)-1 ).Execute(
procedure (const value: integer)
var Chain : string;
begin
Chain := Function1(listOfThings[value]);
if Evaluate(Chain , Solution) then
Parameters[value] := Solution
else
begin
Result := 'ERROR'; //Here is where it won't work
exit;
end;
end
);
end;
Inside the Parallel.ForEach I can't do Result := 'ERROR' because it is not captured inside the procedure, so I think if I can cancel the Parallel.ForEach and report that cancellation, then I can just assign Result := 'ERROR' outside.
But I am new to OmniThreadLibrary and I don't know how to do such a thing, please help me :)

You need to use a cancellation token:
var
cancelToken: IOmniCancellationToken;
You obtain the cancellation token by calling CreateOmniCancellationToken from the OtlSync unit.
cancelToken := CreateOmniCancellationToken;
You then supply the token to the parallel loop:
Parallel.ForEach(...)
.CancelWith(cancelToken)
.Execute(...);
And you signal the cancellation token by calling its Signal method.
cancelToken.Signal;
From outside the parallel loop you can use
cancelToken.IsSignaled
to detect that you cancelled. Or you can capture a boolean variable from the surrounding scope and pass the information through that variable.
The example here gives an illustration.

The cancellation token is only half of it. If you need it to return a value, you'll need to use Aggregate, because you can have an arbitrary number of elements in the sequence, but only one return value, so you need to collapse (aggregate) an arbitrary number of return values into one final value. So you want something like this:
function SomeFunction() : string;
var
cancelToken: IOmniCancellationToken;
error: TOmniValue;
begin
cancelToken := CreateOmniCancellationToken;
error := Parallel.ForEach(0, length(listOfThings)-1 ).
CancelWith(cancelToken).
Aggregate('',
procedure(var aggregate: TOmniValue; const value: TOmniValue)
var Chain : string;
begin
Chain := Function1(listOfThings[value]);
if Evaluate(Chain , Solution) then
Parameters[value] := Solution
else
begin
aggregate := 'ERROR';
cancelToken.signal;
end;
end).
Execute(
procedure(const value: TOmniValue; var result: TOmniValue)
begin
if value <> '' then
result := value;
end);
if error <> '' then
//something went wrong
end;
This may not be exactly perfect, but it should get you on the right track.

Related

How to pass a Var parameter to a Thread

I have thoses objects :
TmyObject1=class
public
Status: integer;
end;
TmyObject2=class
public
StatusA: integer;
StatusB: integer;
end;
I have many objects like those one with many status fields declared. I would like to call a function and let the function update the value of one of the status field. quite easy I declare the function like this :
function MyFunction(var AStatus: Integer);
and I call it like this
MyFunction(myObject1.Status);
or for example
MyFunction(myObject2.StatusB);
that good but now my problem arrive, in MyFunction I create a thread and I want to let the possibility to the thread to update also the value of Status. something like this :
function MyFunction(var AStatus: Integer);
begin
MyTread.??ObjectStatus?? := AStatus;
MyTread.start;
end;
procedure TmyThread.execute;
begin
...
??ObjectStatus?? := NewStatus
...
end;
How can I do ? what type must be the TmyThread.??ObjectStatus??. I was thinking to gave to MyTread a pointer address but I m afraid that if memory relocation between the start and the end of the thread that the pointer address could become wrong (Code must work on ios/android/windows/etc.). any other options to solve my problem ?
Instead of a var parameter you can provide getter and setter to your function:
function MyFunction(GetStatus: TFunc<Integer>; SetStatus: TProc<Integer>);
begin
MyThread.GetStatus := GetStatus;
MyThread.SetStatus := SetStatus;
MyThread.start;
end;
procedure TmyThread.execute;
begin
...
OldStatus := GetStatus;
SetStaus(NewStatus);
...
end;
Calling this function requires some anonymous methods now:
MyFunction(
function: Integer
begin
result := myObject1.Status;
end,
procedure(Arg: Integer)
begin
myObject1.Status := Arg
end);
Of course you have to make sure that myObject1 is available during the thread execution.

How to get TWebBrowser error information

I'm implementing the Exec method of TWebBrowser based on this answer. This method is triggered whenever a script error occurs. Now I need to get error information.
I first get hold of the event object of the TWebBrowser.
var
Doc: IHTMLDocument2;
HTMLWindow: IHTMLWindow2;
Event: IHTMLEventObj;
MethodName: String;
MethodResult: OleVariant;
DispatchId: Integer;
Param: array of OleVariant;
begin
//Avoid non-error calls
if nCmdID != OLECMDID_SHOWSCRIPTERROR then
Exit;
//Get hold of the event object
Doc := MapForm.WebBrowser.Document as IHTMLDocument2;
if not Assigned(Doc) then
Exit;
HTMLWindow := Doc.parentWindow;
if not Assigned(HTMLWindow) then
Exit;
//Get the current event
Event := HTMLWindow.event;
And then I'm trying to get the information I need (as demonstrated in this link) using GetIDsOfNames and Invoke functions of the interface. A working Delphi code for using these methods are in this documentation link.
Here is how I use these functions on the Event object.
MethodName := 'errorMessage';
Result := Event.GetIDsOfNames(GUID_NULL, #MethodName, 1, SysLocale.DefaultLCID, #DispatchId);
Result := Event.Invoke(DispatchId, GUID_NULL, SysLocale.DefaultLCID, DISPATCH_METHOD, Param, #MethodResult, nil, nil);
The GetIDsOfNames fuGetIDsOfNames function executes properly, outputs an acceptable integer to DispatchId and returns S_OK.
But the Invoke function just fails. It returns some negative integer as HRESULT and doesn't output anything to MethodResult.
How can I work around this?
The error values you are trying to access are not object methods, they are properties, so Invoke() is going to fail due to your use of DISPATCH_METHOD. Use DISPATCH_PROPERTYGET instead.
However, OleVariant (and Variant) has built-in support for IDispatch.Invoke(), so you don't need to mess with it manually at all. You can call object methods and read/write object properties normally, and the compiler will produce the necessary IDispatch calls for you.
Try something more like this:
function TWebBrowser.Exec(CmdGroup: PGUID; nCmdID, nCmdexecopt: DWORD;
const vaIn: OleVariant; var vaOut: OleVariant): HRESULT; stdcall;
var
Event: OleVariant;
ErrorLine: Integer;
ErrorChar: Char;
ErrorCode: Integer;
ErrorMsg: String;
ErrorUrl: String;
begin
if (CmdGroup = nil) or not IsEqualGUID(CmdGroup^, CGID_DocHostCommandHandler) then
begin
Result := OLECMDERR_E_UNKNOWNGROUP;
Exit;
end;
if nCmdID <> OLECMDID_SHOWSCRIPTERROR then
begin
Result := OLECMDERR_E_NOTSUPPORTED;
Exit;
end;
Event := (IUnknown(vaIn) as IHTMLDocument2).parentWindow.event;
ErrorLine := Event.errorLine;
ErrorChar := Event.errorCharacter;
ErrorCode := Event.errorCode;
ErrorMsg := Event.errorMessage;
ErrorUrl := Event.errorUrl;
...
if (should continue running scripts) then
begin
vaOut := True;
end else
begin
vaOut := False;
end;
Result := S_OK;
end;

How to properly start, working and finish a transaction?

I'm using MySQL, and I know that Nested Connection are not allowed - use "save points" for this - but I would like create a more generic code that could also be used with other DBMS.
So, I would like know how to properly start, working and finish a transaction in the code below?
Once ExampleDAO.Save() function could be used inside other function, like OtherExampleDAO.Save(), I need verify a transaction has been started before I try start a new one.
The lines with the verification if Assigned(dbTransaction) then always returns true, so how to properly verify if dbTransaction was instantiated?
function TExampleDAO.Save(const Example: TExample): Boolean;
var
dbxTransaction: TDBXTransaction;
begin
if Assigned(Example) then // prevents invalid object, like ExampleDAO.Save(nil);
begin
try
if (_connection.TransactionsSupported) AND
((not _connection.InTransaction) OR (_connection.MultipleTransactionsSupported)) then
begin
dbxTransaction := _connection.BeginTransaction(TDBXIsolations.ReadCommitted);
end;
try
// example
_sqlQuery.Close;
_sqlQuery.SQL.Clear;
_sqlQuery.SQL.Add('INSERT INTO example(a, b) '
+ 'VALUES(:a, :b)');
_sqlQuery.ParamByName('a').AsAnsiString := Example.A;
_sqlQuery.ParamByName('b').AsDateTime := Example.B;
_sqlQuery.ExecSQL(False);
// example info
_sqlQuery.Close;
_sqlQuery.SQL.Clear;
_sqlQuery.SQL.Add('INSERT INTO example_info(c, d) '
+ 'VALUES(:c, :d)');
_sqlQuery.ParamByName('c').AsInteger := Example.Info.C;
_sqlQuery.ParamByName('d').AsFloat := Example.Info.D;
_sqlQuery.ExecSQL(False);
if Assigned(dbxTransaction) then
_connection.CommitFreeAndNil(dbxTransaction);
Result := True;
except
on Exc:Exception do
begin
if Assigned(dbxTransaction) then
_connection.RollBackFreeAndNil(dbxTransaction);
raise Exc;
Result := False;
end;
end;
finally
if Assigned(dbxTransaction) then
FreeAndNil(dbxTransaction);
end;
end
else
begin
Result := False;
end;
end;
You need to properly initialize dbxTransaction to nil at the start of your function. Local variables in Delphi (on the Win32 platform, at least) are not initialized until a value is assigned to them, meaning that the content is unknown. Passing any value other than nil to Assigned will result in True. I recommend never testing a local variable's content on any platform until it has had a value assigned in your code.
Here's an example of how to make it work. (I've also removed the unnecessary assignment to Result in the exception block.)
function TExampleDAO.Salve(const Example: TExample): Boolean;
var
dbxTransaction: TDBXTransaction;
begin
dbxTransaction := nil; // Initialize the transaction variable here
if Assigned(Example) then // prevents invalid object, like ExampleDAO.Save(nil);
begin
try
if (_connection.TransactionsSupported) AND
((not _connection.InTransaction) OR (_connection.MultipleTransactionsSupported)) then
begin
dbxTransaction := _connection.BeginTransaction(TDBXIsolations.ReadCommitted);
end;
try
// example
_sqlQuery.Close;
_sqlQuery.SQL.Clear;
_sqlQuery.SQL.Add('INSERT INTO example(a, b) '
+ 'VALUES(:a, :b)');
_sqlQuery.ParamByName('a').AsAnsiString := Example.A;
_sqlQuery.ParamByName('b').AsDateTime := Example.B;
_sqlQuery.ExecSQL(False);
// example info
_sqlQuery.Close;
_sqlQuery.SQL.Clear;
_sqlQuery.SQL.Add('INSERT INTO example_info(c, d) '
+ 'VALUES(:c, :d)');
_sqlQuery.ParamByName('c').AsInteger := Example.Info.C;
_sqlQuery.ParamByName('d').AsFloat := Example.Info.D;
_sqlQuery.ExecSQL(False);
if Assigned(dbxTransaction) then
_connection.CommitFreeAndNil(dbxTransaction);
Result := True;
except
on Exc:Exception do
begin
if Assigned(dbxTransaction) then
_connection.RollBackFreeAndNil(dbxTransaction);
raise Exc;
end;
end;
finally
if Assigned(dbxTransaction) then
FreeAndNil(dbxTransaction);
end;
end
else
begin
Result := False;
end;
end;
As was noted by #SirRufo in the comments to your question, failing to pass Example as a parameter should probably raise an exception as well, which would mean that it could become a procedure instead of a function and Result would no longer apply at all.

compact delphi code , handling TStringlist as parameter

the following line of code fails :
...
ProcessStringLIst(ChecklistBox_CheckedStrings(MyCheckListBox));
....
// support functions
function ProcessStringLIst (alst : TStringlist);
begin
/// .... process the stringlist
end;
function ChecklistBox_CheckedStrings(aCheckListBox: TCheckListBox): TStringList;
var
i: Integer;
begin
result.Clear;
for i := 0 to aCheckListBox.Items.Count - 1 do
if aCheckListBox.Checked[i] then
result.Add(aCheckListBox.Items[i])
end;
because inside ChecklistBox_CheckedStrings the result is not yet assignd with data . Can I avoid the 4 line version as below :
templist := TStringlist.Create;
temList := ChecklistBox_CheckedStrings(MyCheckListBox);
ProcessStringLIst(templist);
templist.free;
In your code the function return value is not initialized, and then your first line of code does:
result.Clear;
Because you have not initialized result, anything can happen. If you enable compiler warnings then the compiler will tell you this.
You need to make up your mind whether or not you want the function to return a newly created string list object, or to work with one created by the caller. Let's assume you opt for the latter. Then your function becomes a procedure like this:
procedure GetChecklistBox_CheckedStrings(aCheckListBox: TCheckListBox;
aStringList: TStringList);
var
i: Integer;
begin
aStringList.Clear;
for i := 0 to aCheckListBox.Items.Count - 1 do
if aCheckListBox.Checked[i] then
aStringList.Add(aCheckListBox.Items[i])
end;
The calling code then becomes:
templist := TStringlist.Create;
try
GetChecklistBox_CheckedStrings(MyCheckListBox, templist);
ProcessStringList(templist);
finally
templist.free;
end;
You must use try/finally if you want to protect your program against memory leaks.
And the other way looks like this:
function CreateCheckListBox_CheckedStrings(aCheckListBox: TCheckListBox): TStringList;
var
i: Integer;
begin
Result := TStringList.Create;
try
for i := 0 to aCheckListBox.Items.Count - 1 do
if aCheckListBox.Checked[i] then
Result.Add(aCheckListBox.Items[i])
except
Result.Free;
raise;
end;
end;
.....
templist := CreateCheckListBox_CheckedStrings(MyCheckListBox);
try
ProcessStringList(templist);
finally
templist.free;
end;
Again, it's easy to put either of these methods into a class helper to make the calling syntax look more clear.
My most important pieces of advice:
Enable warnings and hints and make sure your code never has any.
Use try/finally correctly.

Delphi 2010+ and "Left side cannot be assigned to" in read-only records: can this be disabled?

I know what changed. I know why. But..
TComplicatedCallMaker = record
Param1: TRecordType;
Param2: TRecordType;
{...}
Param15: TRecordType;
procedure Call;
end;
function ComplicatedCall: TComplicatedCallMaker;
begin
{ Fill default param values }
end;
procedure DoingSomeWorkHere;
begin
with ComplicatedCall do begin
Param7 := Value7;
Param12 := Value12;
Call;
end;
end;
This has perfectly worked before Delphi 2010. An extremely useful technique for making calls which accept a load of parameters but usually only need two or three. Never the same ones though.
And now it gives... guess what?
E2064: Left side cannot be assigned to.
Can't this helpful new behavior be disabled somehow? Any ideas on how to modify the pattern so it works?
Because seriously, losing such a handy technique (and rewriting a bunch of code) for no apparent reason...
I find it a little surprising that this ever worked but since you say it did I'm sure you are right. I'd guess the change was made without consideration for record methods. Without the ability to call methods then this construct would be rather pointless.
Anyway, the compiler isn't going to let you off the hook on this one so you'll have to do this:
type
TRecordType = record end;
TComplicatedCallMaker = record
Param1: TRecordType;
procedure Call;
end;
function ComplicatedCall: TComplicatedCallMaker;
begin
{ Fill default param values }
end;
procedure DoingSomeWorkHere(const Value: TRecordType);
var
CallMaker: TComplicatedCallMaker;
begin
CallMaker := ComplicatedCall;
with CallMaker do begin
Param1 := Value;
Call;
end;
end;
I... think I did it
I hope Delphi developers see what they make their programmers do!
type
PCallMaker = ^TCallMaker;
TCallMaker = record
Param1: integer;
Param2: integer;
function This: PCallMaker; inline;
procedure Call; inline;
end;
function TCallMaker.This: PCallMaker;
begin
Result := #Self;
{ Record functions HAVE to have correct self-pointer,
or they wouldn’t be able to modify data. }
end;
procedure TCallMaker.Call;
begin
writeln(Param1, ' ', Param2);
end;
function CallMaker: TCallMaker; inline
begin
Result.Param1 := 0;
Result.Param2 := 0;
end;
procedure DoingSomeWorkHere;
var cm: TCallMaker;
begin
{Test the assumption that cm is consistent}
cm := CallMaker;
if cm.This <> #cm then
raise Exception.Create('This wasn''t our lucky day.');
{Make a call}
with CallMaker.This^ do begin
Param1 := 100;
Param2 := 500;
Call;
end;
end;
This works, preserves all the good points of the old version (speed, simplicity, small call overhead) but aren't there any hidden problems with this approach?

Resources