Tuesday, June 26, 2007

Delphi Tips And Tricks: CoInitialize/CoUninitialize Part III

Previously I talked about using CoInitialize and CoUninitialize in very simple situations, a single threaded console application. If you havn't read those posts you can read Part I here, Part II here and the adendum to Part II here. So for this post I'm going to demonstrate the use of CoInitializeEx and CoUninitialize in a multithreaded environment.

This demo consists of two files, two instances of a thread and a couple loops to give your system enough time to actually allow some processing to happen. Below find the code for this demo:


program Project1;

{$APPTYPE CONSOLE}

uses
SysUtils,
ActiveX,
MyThread in 'MyThread.pas';

var
Thread1, Thread2: TMyThread;
Success: HResult;
begin
Success := CoInitializeEx(nil, COINIT_APARTMENTTHREADED);

try
Thread1 := TMyThread.Create(False, 'thread1');
Thread2 := TMyThread.Create(False, 'thread2');

try
// Note my implementation could have a
// problem if DoStuff completes too quicly.

DoStuff('main');
Thread1.Wait;
Thread2.Wait;
finally
Thread1.Free;
Thread2.Free;
end;
finally
CoUninit(Success, 'main');
end;
end.




unit MyThread;

interface

uses
Classes, SyncObjs;

type
TMyThread = class(TThread)
private
FLock: TCriticalSection;
FText: string;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean; const Text: string);
destructor Destroy; override;
procedure Wait;
end;

procedure CoInitStatus(Success: HResult);
procedure CoUninit(Success: HResult; const Text: string);
procedure DoStuff(const Text: string);

implementation

uses
SysUtils, ActiveX, Windows;

procedure CoInitStatus(Success: HResult);
begin
case Success of
S_OK, S_FALSE: WriteLn('CoInitialize success');
RPC_E_CHANGED_MODE, E_INVALIDARG,
E_OUTOFMEMORY, E_UNEXPECTED: WriteLn('CoInitialize failed');
end;
end;

procedure CoUninit(Success: HResult; const Text: string);
begin
case Success of
S_OK, S_FALSE: CoUninitialize;
end;
end;

procedure DoStuff(const Text: string);
var
Index: Integer;
begin
for Index := 0 to 10000 do
WriteLn(Text + ' ' + IntToStr(Index));
end;

{ TMyThread }

constructor TMyThread.Create(CreateSuspended: Boolean; const Text: string);
begin
FLock := TCriticalSection.Create;
inherited Create(CreateSuspended);
FText := Text;
UniqueString(FText);
end;

destructor TMyThread.Destroy;
begin
FLock.Free;
inherited;
end;

procedure TMyThread.Execute;
var
Success: HResult;
begin
FLock.Acquire;
Success := CoInitializeEx(nil, COINIT_MULTITHREADED);

try
DoStuff(FText);
finally
CoUninit(Success, FText);
FLock.Release;
end;
end;

procedure TMyThread.Wait;
begin
FLock.Acquire;
FLock.Release;
end;

end.


I want to bring your attention to two things. First, notice my use of the return value from CoInitializeEx. And second, the first thread to call CoInitializeEx (located in the main function) is also the last one to call CoUninitialize by use of the the function TMyThread.Wait.

I would talk more about it but I think the code explains everything. So read the source and if you have questions ask.

Update: I have moved the critical section to occur before the constructor to TThread to avoid a race condition and called UniqueString on the string passed to the thread as pointed out by Anders Melander.

13 comments:

Fernando Madruga said...

"And second, the first thread to call CoInitializeEx (located in the main function) is also the last one to call CoUninitialize by use of the the function TMyThread.Wait."

Are you 100% sure of that?

Because I added a few more lines to your sample to do some Writelns in the constructor/destructor and before the CoInitializeEx / CoUninit calls and I got mixed results.

In one case, (I've removed some of the thread WriteLn from the following paste to conserve space), here's what I got:

thread1 10000
thread1 - About to call CoUnInit.
thread2 9957
....
thread2 9974
main 9983
....
main 10000
thread2 9975
....
thread2 10000
thread2 - About to call CoUnInit.
thread1 - Freed.
thread2 - Freed.


Now, in all honesty, I don't know if in this particular case the threads were created in the opposite order. Trying to redirect to a file will only show the writelns from the main thread and 10000 is a tad more than I have in scrollback in 4NT! :)

Fernando Madruga said...

Here's another sample this time a full one (edited for size), after reducing to just 2000 iterations:

thread1 - Created.
thread2 - Created.
main 0
main 1
main 2
main 2
thread1 - Called CoInitializeEx.
thread1 0
....
thread1 14
thread1 14
thread2 - Called CoInitializeEx.
thread2 0
....
thread2 14
main 3
....
....
thread2 1977
....
thread2 1994
thread1 1995
....
thread1 2000
thread1 - About to call CoUnInit.
thread2 1995
....
thread2 2000
thread2 - About to call CoUnInit.
thread1 - Freed.
thread2 - Freed.

Sebastian said...

There seems to be a race condition in there: If you call wait right after the creation of the thread, your wait function might acquire the critical section before the Execute function does. I've had a problem like this in a project of mine.

What is the advantage of using the own Wait function instead of Thread1.WaitFor?

Users of earlier delphi versions should be careful, because the thread might start before the critical section has been created.

You will find another possible problem if you change the main program so that it says:
try
// DoStuff('main');
Thread1.Wait;
Thread2.Wait;
Thread1.WaitFor;
THread2.WaitFor;
finally
Do not call DoStuff this time (this simulatest that DoStuff did its job faster than expected). We need to add an additional call to Thread1.WaitFor because of the race condition mentioned above.
Now you will to get an access violation on shut down. The reason is that the Critical Section will be destroyed too early. Calling Thread.Free waits until the thread has finished. But code before the inherited will be called while the thread is still running.
So I'd recommend that you change the destructor like this:
destructor TMyThread.Destroy;
begin
inherited;
FLock.Free;
end;
Typically the call to inherited should be the last thing in a constructor. In this case it might create a random access violation. But you can destroy your own objects (those that are not inherited) after the call to inherited as far as I know.

Ralph Gielkens said...

Hello, i have the following question, I have 2 threads and i want to create 2 the same OLE objects in booth threads. When i create it like this CoInitializeEx(COINIT_APARTMENTTHREADED) do i get 2 individual OLE objects?? Now i have spited the threads in 2 services.

Chris Bensen said...

Fernando and Sebastian,

My example was contrived as all examples are to demonstrate a specific point, using CoInitialize in a multithreaded environment. Both of you found a problem when DoStuff performs too quickly in main.

WaitFor wasn't used because this was originally a larger example. Using WaitFor would be the prefered method in this case, but both have the same result.

Chris Bensen said...

Ralph,

I don't understand your question?

Anders Melander said...

There's a much more serious race condition in the code. If CreateSuspended is False your code will likely cause an AV on SMP systems.

The call to inherited Create(False) will cause the thread to be created and resumed immediately. If the thread reaches the call to FLock.Acquire before the critical section has been created, then you'll have an AV.

The correct way to do this would be:

TMyThread.Create(CreateSuspended: Boolean; const Text: string);
begin
  inherited Create(True);
  FText := Text;
  FLock := TCriticalSection.Create;
  if (not CreateSuspended) then
    Resume;
end;

Additionally, since the string is reference counted and thus shared between the two threads, it should probably be protected somehow. Most easily by calling UniqueString on it.

Chris Bensen said...

Would "find the race condition" or "find the deadlock" would be a fun for everyone?

Anders,

Good catch. I haven't run into that race condition but it's still there. I have now fixed the code.

Ralph Gielkens said...

Hello Chris,

I shall try to make my point; I'm using my own created OLEObject. And I have two TMyThread objects in one service application. In the execute from both my threads have the same code like this:

Procedure TMyThread.Execute;
var MyOLEObj : olevariant;
begin
Coinitialize;
try
MyOLEObj := CreateOLEObject ('MyOleObject');
while not terminated do
begin
//Do something with MyOLEOBJ
end;
finally
Couninitialize;
end;
end;

When I created both threads and changed a property of MyOLEObj in the first thread I have also changed the property of MyOLEObj in the second thread because I have the same instance of MyOleObject. Can I solve this problem with:

Procedure TMyThread.Execute;
var MyOLEObj : olevariant;
begin
CoInitializeEx(COINIT_APARTMENTTHREADED);
try
MyOLEObj := CreateOLEObject ('MyOleObject');
while not terminated do
begin
//Do something with MyOLEOBJ
end;
finally
Couninitialize;
end;
end;

Chris Bensen said...

Ralph,

So you are creating two instances of the same COM object, one in each thread, and when modifying a property on the object you affect the property of the instance in the other thread?

The first thought that comes to mind is that your COM object is a singleton or the property shares the same variable instance. If you have the source code then that should be fairly easy to track down. If you only have the binary then it will make things a bit more difficult.

Ralph Gielkens said...

Hello Chris,

The source code of the Com Object I cant give you because its from my work. You said:
>The first thought that comes to mind >is that your COM object is a >singleton or the property shares the >same variable instance.

The property doesn't shares the same variable instance.

The term "singleton"?? never heard about it. can you give an example how you can see if the COM Object is a singleton.

Chris Bensen said...

Ralph,

Can you click the "Contact Me" link on the right side of my blog so we can take this offline?

Rafael Lima said...

Thank's man :D
Solv my problem here!

Post a Comment