Related to this question, I am trying to implement a procedure that uses the WinRT API to set the desktop wallpaper. To mimic the await functionality in C#, I am using TTask.Future (link) as outlined here and here.
My implementation looks like this:
class procedure TUtilityWin.SetWallpaper(AFileName: String);
var
lStorageFile: IStorageFile;
liao_storagefile: IAsyncOperation_1__IStorageFile;
lFutureTask: IFuture<IAsyncOperation_1__IStorageFile>;
begin
//WinRT Implementation
if TUserProfile_UserProfilePersonalizationSettings.IsSupported then
begin
lFutureTask:=TTask.Future<IAsyncOperation_1__IStorageFile>(
function: IAsyncOperation_1__IStorageFile
begin
Result:=TStorageFile.GetFileFromPathAsync(HSTRING(AFileName));
end);
liao_storagefile:=lFutureTask.Value;
lStorageFile:=liao_storagefile.GetResults;
TUserProfile_UserProfilePersonalizationSettings.Current.TrySetWallpaperImageAsync(lStorageFile);
end;
end;
Per my understanding, when I try to get lFutureTask.Value, the application suspends the current thread until lFutureTask is completed (if it is not already) and then provides the value. However, when I run the application, I get the error message: EOleException with message 'A method was called at an unexpected time'. The break is on this line: lStorageFile:=liao_storagefile.GetResults;
I am new to TTask as well as the WinRT API - so I am certain I am missing something very basic here. Would appreciate any pointers on what would be causing this or what I could be doing differently to fix this. Thanks in advance.
I took a look at the Delphi docs linked in your question, and AFAICT both ITask and IFuture only represent methods that execute on a separate thread (what I call "Delegate Tasks"). There doesn't appear to be any support for asynchronous tasks (what I call "Promise Tasks"). IAsyncOperation<T> is a WinRT type representing an asynchronous task, hence the problems you're having. In particular, there doesn't appear to be any Delphi support for registering continuations onto their "task"s.
So, unless there's some Delphi support for a non-threaded Future/Promise, you're going to have to block a thread.
Currently, your code is spinning up a threaded task that only starts the asynchronous operation (GetFileFromPathAsync). The threaded task is not waiting for the asynchronous operation to complete (IAsyncOperation<T>.Completed), so that task completes immediately after starting the operation, and then the outer code calls GetResult when the operation doesn't have a result yet, causing the exception.
So, to fix this, you'll need a way to have the threaded task block until the asynchronous operation completes. Since WinRT types are purely asynchronous (with no support for synchrony), and since the Delphi types are purely synchronous (with no support for asynchrony), you'll have to bridge it yourself. The best solution is probably the Delphi equivalent of a ManualResetEvent.
Something like this should work:
class procedure TUtilityWin.SetWallpaper(AFileName: String);
var
lStorageFile: IStorageFile;
lFutureTask: IFuture<IStorageFile>;
begin
if TUserProfile_UserProfilePersonalizationSettings.IsSupported then
begin
lFutureTask:=TTask.Future<IStorageFile>(
function: IStorageFile
var
liao_storagefile: IAsyncOperation_1__IStorageFile;
mre: ManualResetEvent;
result: IStorageFile;
begin
mre:= // Create ManualResetEvent
liao_storagefile:=TStorageFile.GetFileFromPathAsync(HSTRING(AFileName));
liao_storagefile.Completed... // Add handler that will set `result` and then set the ManualResetEvent
mre.Wait(); // Wait for the ManualResetEvent to be set
Result:=result;
end);
liao_storagefile:=lFutureTask.Value;
lStorageFile:=liao_storagefile.GetResults;
TUserProfile_UserProfilePersonalizationSettings.Current.TrySetWallpaperImageAsync(lStorageFile);
end;
end;
Sorry, I don't know Delphi, but hopefully this will give you a general direction.
The following (handling an asynchronous call to WebAuthenticationCoreManager.FindAccountProviderAsync) works for me, even though it is as ugly as it gets compared to async/await in .Net:
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Winapi.Winrt,
System.Win.WinRT,
Winapi.Security.Credentials,
Winapi.Security,
Winapi.Foundation.Types
...
type
TCompleteOperationCompleted_IWebAccountProvider = reference to procedure (Value : IWebAccountProvider);
TCompleteOperationError_IWebAccountProvider = reference to procedure ();
TAsyncOperationCompletedHandler_1__IWebAccountProvider = class (TInterfacedObject,
AsyncOperationCompletedHandler_1__IWebAccountProvider_Delegate_Base,
AsyncOperationCompletedHandler_1__IWebAccountProvider)
private
fOnCompleted : TCompleteOperationCompleted_IWebAccountProvider;
fOnError : TCompleteOperationError_IWebAccountProvider;
protected
procedure Invoke(asyncInfo: IAsyncOperation_1__IWebAccountProvider; asyncStatus: AsyncStatus); safecall;
public
constructor Create(OnCompleted : TCompleteOperationCompleted_IWebAccountProvider;
OnError : TCompleteOperationError_IWebAccountProvider = nil);
end;
constructor TAsyncOperationCompletedHandler_1__IWebAccountProvider.Create(
OnCompleted: TCompleteOperationCompleted_IWebAccountProvider;
OnError: TCompleteOperationError_IWebAccountProvider);
begin
fOnCompleted := OnCompleted;
fOnError := OnError;
end;
procedure TAsyncOperationCompletedHandler_1__IWebAccountProvider.Invoke(
asyncInfo: IAsyncOperation_1__IWebAccountProvider; asyncStatus: AsyncStatus);
begin
case asyncStatus of
Winapi.Foundation.Types.AsyncStatus.Completed : if Assigned(fOnCompleted) then fOnCompleted(asyncInfo.GetResults());
Winapi.Foundation.Types.AsyncStatus.Error : if Assigned(fOnError) then fOnError();
else ;//todo
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
const
DefaultProviderId = 'https://login.windows.local';
MicrosoftProviderId = 'https://login.microsoft.com';
var
account: IAsyncOperation_1__IWebAccountProvider;
webAccount: IWebAccountProvider;
begin
account := TAuthentication_Web_Core_WebAuthenticationCoreManager.FindAccountProviderAsync(TWindowsString.Create(MicrosoftProviderId{DefaultProviderId}));
account.Completed := TAsyncOperationCompletedHandler_1__IWebAccountProvider.Create(
procedure(Value : IWebAccountProvider)
begin
ShowMessage('Async operation completed');
//todo
end,
nil
);
end;
Related
Like many other Firemonkey developers, I need a general multi-platform solution to send messages from a thread to the main thread (to replace PostMessage). I need it to also work on iOS.
There is a solution by François Piette that is implemented for Android and Windows, but not for iOS:
TMessagingSystem.
However, I think it can be done much more simple by using the "new" TMessageManager in combination with TThread.Queue(). But no one have published code, using this aproach, that actually works (e.g. this one is not complete).
Do you have a tested implementation you would like to share with the community (or maybe just suggestions how to implement it right)?
Ok, here is my implementation. I did not use TMessagingSystem as it seems to just add complexity (for my situation at least). It works so far, but if anyone have suggestions for improvements, I will be happy to improve it.
I looked at the solution by Uwe Raabe but I wanted to make it more straightforward and easy to implement in the large codebase that I am converting to FMX.
With the solution below I can simply replace all PostMessage() with gMessageHandler.PostMessage (removing the win handle argument), and add the message functions in the form to tMainForm.MessageCallBack.
I created a small unit that I can include everywhere I need the PostMessage function. Those places does not need to know about the form:
unit MessageHandler
interface
tAllOSMessage = procedure(aMessageID, aData1, aData2: integer) of object;
tAllOSMessageHandler = class
private
fOnMessage : tAllOSMessage;
public
constructor Create(aMessageCallBack: tAllOSMessage);
procedure PostMessage(aMessageID, aData1, aData2: integer; aSourceThread: TThread = nil);
end;
var
gMessageHandler: tAllOSMessageHandler;
implementation
constructor tAllOSMessageHandler.Create(aMessageCallBack: tAllOSMessage);
begin
fOnMessage := aMessageCallBack;
end;
procedure tAllOSMessageHandler.PostMessage(aMessageID, aData1, aData2: integer; aSourceThread: TThread);
begin
if aSourceThread=nil then
aSourceThread := TThread.CurrentThread;
aSourceThread.Queue(nil, procedure
begin
if Assigned(fOnMessage) then
fOnMessage(aMessageID, aData1, aData2);
end );
end;
end.
Then I add these lines to the main form unit:
//Added to main form:
tMainForm = class(TForm)
...
procedure MessageCallBack(aMessageID, aData1, aData2: integer);
//Added to MainFormCreate
gMessageHandler := tAllOSMessageHandler.Create(MessageCallBack);
//Added to MainFormDestroy
FreeAndNil(gMessageHandler)
procedure tMainForm.MessageCallBack(aMessageID, aData1, aData2: integer);
begin
case aMessageID of
MyMessage1 : MyFunction1(aData1,aData2);
...
end;
end;
I am currently testing two external COM components. I have big issue with one of them, but I cannot really find reason behind such behavior. Let me provide some example.
const
CLASS_SomeClas: TGUID = '{SomeGUID}';
type
ISomeInterface = interface(IDispatch)
['{SomeGUID}']
function SomeMethod(const AInput: WideString): WideString; safecall;
end;
TWrappingClass = class(TObject)
strict private
FInstance: ISomeInterface;
procedure CreateInstance;
public
procedure DoYourActualJob;
end;
procedure TWrappingClass.CreateInstance;
begin
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
dbg(FInstance._AddRef); // Debugs 3
dbg(FInstance._AddRef); // Debugs 4
dbg(FInstance.Release); // Debugs 3
dbg(FInstance._AddRef); // Debugs 4
FInstance.SomeMethod(''); //Runs as expected
end;
procedure TWrappingClass.DoYourActualJob;
begin
CreateInstance;
dbg(FInstance._AddRef); //Debugs -1!
FInstance.SomeMethod(''); //AV
end;
As provided with example instance gets invalid after it leaves CreateInstance method. Component is designed to work with many sequential calls of SomeMethod and it does work when called inside single method.
Could someone give me clue what is actually happening there, why my instance gets invalid? Is it problem with my code, with Delphi or with component's code? When I change the implementation of TWrappingClass to another vendor (that is I change both ISomeInterface and CLASS_SomeClass) then everything works fine.
EDIT:
Behaviour does not change when I don't even call SomeMethod. That is after I leave CreateInstance, call to _AddRef returns -1. Component I am testing is here CadEditorX Probably I am not allowed to attach the OCX without violating its license.
You state clearly in the question that the erroneous behaviour only occurs with one specific COM object. Given this fact, and that Delphi's COM reference counting is known to work correctly, the only reasonable conclusion is that the fault lies in this specific COM object.
Your only recourse of action is to contact the vendor of this COM object and file a bug report with them.
One thing to look at, with a view to a possible work around, is how you are creating the object. You use CreateComObject. This receives a class ID and returns IUnknown. It calls CoCreateInstance passing the class ID, and requesting the IUnknown interface. You then need to query for your interface, ISomeInterface. So your code looks like this:
var
iunk: IUnknown;
intf: ISomeInteface;
....
CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER,
IUnknown, iunk);
iunk.QueryInterface(ISomeInterface, intf);
The fact that you have two interface variables, one IUnknown and one ISomeInterface explains why you see the reference count that you do. Now, you might think that you only have one interface variable, but that's not the case. There are two, only one of them is an implicit local. You can see this by looking at the compiled code and stepping through under the debugger.
This code:
procedure TWrappingClass.CreateInstance;
begin
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
end;
is compiled as if it were this (ignoring error checking):
procedure TWrappingClass.CreateInstance;
var
iunk: IUnknown;
begin
iunk := CreateComObject(CLASS_SomeClass);
try
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
finally
iunk := nil;
end;
end;
Perhaps the COM component cannot handle the call to Release made on its IUnknown interface.
So, you could try to work around this by using CoCreateInstance instead of CreateComObject. Pass ISomeInterface as the riid parameter.
OleCheck(CoCreateInstance(CLASS_SomeClass, nil, CLSCTX_INPROC_SERVER
or CLSCTX_LOCAL_SERVER, ISomeInterface, FInstance));
I am developing application which intend to be cross platform. I used to use Windows Messages but now I am dropping it out. I replaced messages with callbacks but regardless I can use different technologies I am not aware of different possibilites when not using windows messages.
Well I have main exe aplication and some dll plugins. I have some objects and threads in dll and I would like to notify main application about some changes that DLL made to data structure.
As I said I am currently working with some callbacks. To provide compatibility with different languages (C++, VB, C#) I have non-object type of callback. I am not sure if other languages supports callback of object.
So my questions are:
What are the alternatives (cross-platform) to windows messages? Can callbacks replace messages?
Do other languages support callback of object?
I guess other languages have different technologies as alternative to messages?
You can certainly use callback functions instead of messages. You can't use callback methods because only Delphi and C++ Builder understand how to invoke Delphi method pointers. However, you can use callback objects with any language that supports COM. Here's an example for a plug-in to notify the application that the data structure has changed:
Define an interface.
type
IDataStructureChanged = interface
['{GUID}']
procedure Call; stdcall;
end;
You could add some parameters to the method so the plug-in can tell how the data structure changed, or pass some value indicating which plug-in is making the notification.
Implement it in the application.
type
TDataStructureChangedListener = class(TInterfacedObject, IDataStructureChanged)
private
FForm: TForm;
procedure Call; stdcall;
public
constructor Create(Form: TForm);
end;
When you instantiate that class, you can pass it a reference to your program's main form, or whatever other information your program will need to be able to take action when a plug-in eventually calls the Call method. Implement Call to make your application do whatever it needs to do when a data structure changes.
Pass a reference to each of the plug-ins when you initialize them.
ChangeListener := TDataStructureChangedListener.Create(Self);
for i := 0 to Pred(PlugIns.Count) do
PlugIns[i].Init(ChangeListener);
The plug-in should store a reference to the listener object, and when the data structure changes, it can call the Call method to notify your application.
What I've described here is what's generally known as an event sink. You can have more than one in your program. If there are multiple events to handle, you could have a separate interface for each kind of event, or you could group them all into a single interface and have a different method for each event. You could have a different sink object for each plug-in, or you could give each plug-in a reference to the same sink object, and then pass a plug-in-ID parameter.
I would definately use callbacks. The main app could give a callback function to the DLL to call when needed, and then the callback function itself can send window messages to the app if it needs to.
I agree with Remy, (!). A straightforward callback allows the handler to implement any kind of further communication it chooses - it might post a message, it may push a parameter onto a queue, whatever it wants. If you want to be cross-platform, you are going to have to resort to passing in, and out, simple types. It's usual to pass in a 'user context' pointer when callbacks are set up. The callback passes this pointer into the handler. This allows callers to pass in a context object as a pointer/int and to recover it in the handler, (by casting the pointer/int back to an object). The handler can then call methods on the context, no matter whether it's Delphi, C++ etc.
So my questions are:
What are the alternatives (cross-platform) to windows messages? Can callbacks replace messages?
Yes you can replace messages with callbacks.
Do other languages support callback of object?
You shouldn't use object methods as callbacks. Common practice in portable code is use of handles (notify calling convention):
DLL source:
type
THandle = LongWord;
{$IF SizeOf(THandle) < SizeOf(Pointer))}
{$MESSAGE Error 'Invallid handle type'}
{$ENDIF}
TCallback = procedure(const aHandle: THandle); cdecl;
var
gCallback: record
Routine: TCallback;
Obj: TObject;
Info: string
end;
function Object2Handle(const aObj: TObject): THandle;
begin
Result:= THandle(Pointer(aObj))
end;
function Handle2Object(const aHandle: THandle; out aObj: TObject): Boolean;
begin
if gCallback.Obj <> nil then
if aHandle = Object2Handle(gCallback.Obj) then
begin
aObj:= gCallback.Obj;
Result:= true;
Exit // WARRNING: program flow disorder
end;
aObj:= nil;
Result:= false
end;
procedure DoCallback();
begin
if Assigned(gCallback.Routine) then
gCallback.Routine(Object2Handle(gCallback.Obj))
end;
procedure SetupCallback(const aCallback: TCallback); cdecl;
begin
gCallback.Routine:= aCallback;
end;
procedure DoSomething(const aHandle: THandle; out aInfo: string); cdecl;
var
O: TObject;
begin
if Handle2Object(aHandle, O) then
aInfo:= Format('%s class object %s', [O.ClassName(), gCallback.Info])
end;
procedure Test();
begin
gCallback.Obj:= TStream.Create();
try
gCallback.Info:= 'created';
DoCallback();
finally
FreeAndNil(gCallback.Obj)
end;
gCallback.Obj:= TMemoryStream.Create();
try
gCallback.Info:= 'will be freed';
DoCallback();
finally
FreeAndNil(gCallback.Obj)
end
end;
exports
SetupCallback,
DoSomething,
Test;
Executable source:
procedure Cb(const aHandle: THandle); cdecl;
const
STUPID: THandle = 1;
EQUALLY_STUPID = $DEAD;
var
S: string;
begin
DoSomething(STUPID, S);
DoSomething(aHandle, S);
DoSomething(EQUALLY_STUPID, S)
end;
begin
SetupCallback(#Cb);
Test()
end.
Edited: You can't shoot yourself in you leg now.
I guess other languages have different technologies as alternative to messages?
OS have a few message alternatives. However not many truly portable.
You can also use:
sockets,
(IMO too big in this case?) ready messaging system (my favorite 0MQ)
As much I know - Subroutines are with Private access mode to its parent unction / procedure, right?
Is there any way to access them from "outer-world" - dpr or other function / procedure in unit?
Also - which way takes more calcualtion and space to compiled file?
for example:
function blablabla(parameter : tparameter) : abcde;
procedure xyz(par_ : tpar_);
begin
// ...
end;
begin
// ...
end;
procedure albalbalb(param : tparam) : www;
begin
xyz(par_ : tpar_); // is there any way to make this function public / published to access it therefore enabling to call it this way?
end;
// all text is random.
// also, is there way to call it from DPR in this manner?
// in C++ this can be done by specifing access mode and/or using "Friend" class .. but in DELPHI?
Nested procedures/functions - those declared inside another procedure or function, are a special type, because they can access the stack (and thereby parameters/local variables) of the procedure they are nested in. Because of this, and Delphi scope rules, there is no way to access them outside the "parent" procedure. You use them only if you need to take advantage of their special features. AFAIK Delphi/Pascal is one of the few languages to have this feature. From a compiler point of view the call has some extra code to allow accessing the parent stack frame, IIRC.
AFAIK "friend" class/functions in C++ are different - they are class access methods, while in your example you are using plain procedures/functions.
In Delphi all procedure/classes declared in the same unit are automatically "friend", unless strict private declarations are used in latest Delphi releases. For example this code snippets will work, as long everything is in the same unit:
type
TExample = class
private
procedure HelloWorld;
public
...
end;
implementation
function DoSomething(AExample: TExample);
begin
// Calling a private method here works
AExample.HelloWordl;
end;
Note: Embedded Routines <> Private/Protected Methods.
Embedded routines i.e. routines inside routines can not be accessed by external routines.
You have posted an example of an Embedded routine, I also heard them called Internal Routines.
Here is another example:
procedure DoThis;
function DoThat : Boolean;
begin
// This Routine is embedded or internal routine.
end;
begin
// DoThat() can only be accessed from here no other place.
end;
Regardless of visibility, methods on classes, can be called using Delphi 2010 via RTTI. I have detailed how to do this in this article.
If you are in the same Unit methods on a class can be accessed by any other code regardless of visibility, unless they are marked with Strict Private. This Question has more details and good example code in the accepted answer.
If you are in two different units you can use the Protected Method Hack to access the protected methods. Which is detailed in detailed in this article.
Yes, you can access a subroutine, which is nested in other (parent) subroutine, from the outer world. Though it's somewhat tricky. I've found this howto in the web.
How to pass nested routine as a procedural parameter (32 bit)
Delphi normally does not support passing nested routines as procedural parameters:
// This code does not compile:
procedure testpass(p: tprocedure);
begin
p;
end;
procedure calltestpass;
procedure inner;
begin
showmessage('hello');
end;
begin
testpass(inner);
end;
The obvious workaround is to pass procedure address and typecast it within testpass:
// This code compiles and runs OK
procedure testpass(p: pointer);
begin
tProcedure(p);
end;
procedure calltestpass;
procedure inner;
begin
showmessage('hello');
end;
begin
testpass(#inner);
end;
There is, however, a pitfall in the above example - if the "inner" routine references any variable that was pushed onto the stack before the "inner" procedure was called from testpass (calltestpass parameters - if there were any, or local variables in calltestpass - if there were any), your system most probably crashes:
// This code compiles OK but generates runtime exception (could even be
// EMachineHangs :-) )
procedure testpass(p: pointer);
begin
tProcedure(p);
end;
procedure calltestpass;
var msg: string;
procedure inner;
begin
msg := 'hello';
showmessage(msg);
end;
begin
testpass(#inner);
end;
The reason is, in simple words, that the stack frame arrangement
was "broken" by the call to testpass routine and "inner" procedure
incorrectly calculates parameters and local variables location
(do not blame Delphi, please).
The workaround is to set up the correct stack context before
"inner" is called from within "testpass".
// This code compiles and runs OK
{$O-}
procedure testpass(p: pointer);
var callersBP: longint;
begin
asm // get caller's base pointer value at the very beginning
push dword ptr [ebp]
pop callersBP
end;
// here we can have some other OP code
asm // pushes caller's base pointer value onto stack and calls tProcedure(p)
push CallersBP
Call p
Pop CallersBP
end;
// here we can have some other OP code
end;
{$O+}
procedure calltestpass;
var msg: string;
procedure inner;
begin
msg := 'hello';
showmessage(msg);
end;
begin
testpass(#inner);
end;
Please note the optimization is switched OFF for testpass routine - optimization generally does not handle mixed OP/assembler code very well.
No, there is no way to do what you're asking. The xyz function is callable only by the enclosing blablabla function. Outside that function, xyz is not in scope and there is no way to name it. If C++ allowed nested function, there wouldn't be any way to refer to it, either, just like there's no way to refer to functions with static linkage from outside the current translation unit.
If you need to call xyz from outside the blablabla function, then move xyz outside. If you need to call it from outside the current unit, then you need to declare that function in the unit's interface section. Then, add that unit to the external code's uses clause and you can call xyz from wherever you want, even the DPR file.
If xyz refers to variables or parameters of the blablabla function, then you'll need to pass them in as parameters since xyz will no longer have access to them otherwise.
The concept of access specifiers isn't really relevant here since we're not talking about classes. Units have interface and implementation sections, which aren't really the same as public and private sections of a class.
I created a class that opens a COM port and handles overlapped read and write operations. It contains two independent threads - one that reads and one that writes data. Both of them call OnXXX procedures (eg OnRead or OnWrite) notifying about finished read or write operation.
The following is a short example of the idea how the threads work:
TOnWrite = procedure (Text: string);
TWritingThread = class(TThread)
strict private
FOnWrite: TOnWrite;
FWriteQueue: array of string;
FSerialPort: TAsyncSerialPort;
protected
procedure Execute; override;
public
procedure Enqueue(Text: string);
{...}
end;
TAsyncSerialPort = class
private
FCommPort: THandle;
FWritingThread: TWritingThread;
FLock: TCriticalSection;
{...}
public
procedure Open();
procedure Write(Text: string);
procedure Close();
{...}
end;
var
AsyncSerialPort: TAsyncSerialPort;
implementation
{$R *.dfm}
procedure OnWrite(Text: string);
begin
{...}
if {...} then
AsyncSerialPort.Write('something');
{...}
end;
{ TAsyncSerialPort }
procedure TAsyncSerialPort.Close;
begin
FLock.Enter;
try
FWritingThread.Terminate;
if FWritingThread.Suspended then
FWritingThread.Resume;
FWritingThread.WaitFor;
FreeAndNil(FWritingThread);
CloseHandle(FCommPort);
FCommPort := 0;
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Open;
begin
FLock.Enter;
try
{open comm port}
{create writing thread}
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Write(Text: string);
begin
FLock.Enter;
try
{add Text to the FWritingThread's queue}
FWritingThread.Enqueue(Text);
finally
FLock.Leave;
end;
end;
{ TWritingThread }
procedure TWritingThread.Execute;
begin
while not Terminated do
begin
{GetMessage() - wait for a message informing about a new value in the queue}
{pop a value from the queue}
{write the value}
{call OnWrite method}
end;
end;
When you look at the Close() procedure, you will see that it enters the critical section, terminates the writing thread and then waits for it to finish.
Because of the fact that the writing thread can enqueue a new value to be written when it calls the OnWrite method, it will try to enter the same critical section when calling the Write() procedure of the TAsyncSerialPort class.
And here we've got a deadlock. The thread that called the Close() method entered the critical section and then waits for the writing thread to be closed, while at the same time that thread waits for the critical section to be freed.
I've been thinking for quite a long time and I didn't manage to find a solution to that problem. The thing is that I would like to be sure that no reading/writing thread is alive when the Close() method is left, which means that I cannot just set the Terminated flag of those threads and leave.
How can I solve the problem? Maybe I should change my approach to handling serial port asynchronously?
Thanks for your advice in advance.
Mariusz.
--------- EDIT ----------
How about such a solution?
procedure TAsyncSerialPort.Close;
var
lThread: TThread;
begin
FLock.Enter;
try
lThread := FWritingThread;
if Assigned(lThread) then
begin
lThread.Terminate;
if lThread.Suspended then
lThread.Resume;
FWritingThread := nil;
end;
if FCommPort <> 0 then
begin
CloseHandle(FCommPort);
FCommPort := 0;
end;
finally
FLock.Leave;
end;
if Assigned(lThread) then
begin
lThread.WaitFor;
lThread.Free;
end;
end;
If my thinking is correct, this should eliminate the deadlock problem. Unfortunately, however, I close the comm port handle before the writing thread is closed. This means that when it calls any method that takes the comm port handle as one of its arguments (eg Write, Read, WaitCommEvent) an exception should be raised in that thread. Can I be sure that if I catch that exception in that thread it will not affect the work of the whole application? This question may sound stupid, but I think some exceptions may cause the OS to close the application that caused it, right? Do I have to worry about that in this case?
Yes, you should probably reconsider your approach. Asynchronous operations are available exactly to eliminate the need for threads. If you use threads, then use synchronous (blocking) calls. If you use asynchronous operations, then handle everything in one thread - not necessarily the main thread, but it doesn't make sense IMO to do the sending and receiving in different threads.
There are of course ways around your synchronization problem, but I'd rather change the design.
You can take the lock out of the Close. By the time it returns from the WaitFor, the thread body has noticed it has been terminated, completed the last loop, and ended.
If you don't feel happy doing this, then you could move setting the lock just before the FreeAndNil. This explicitly lets the thread shutdown mechanisms work before you apply the lock (so it won't have to compete with anything for the lock)
EDIT:
(1) If you also want to close the comms handle do it after the loop in the Execute, or in the thread's destructor.
(2) Sorry, but your edited solution is a terrible mess. Terminate and Waitfor will do everything you need, perfectly safely.
The main problem seems to be that you place the entire content of Close in a critical section. I'm almost sure (but you'll have to check the docs) that TThread.Terminate and TThread.WaitFor are safe to call from outside the section. By pulling that part outside the critical section you will solve the deadlock.