MultiThreading an ActiveX DLL call...

C

Cybertof

Hello,

Is there a good way to call a big time-consumming function from an
ActiveX DLL (interoped) through a thread without blocking the main UI ?

Here are the details :

I have a class CInteropCall encapsulating a call to a visual basic
ActiveX DLL function named FillAlloc_ArrayStruct_Double(), which is
allocating a big struct array an fills it using a inner loop
(for i = 1 to 300000...)

I'm calling this BigProcess through a thread from the main UI
like this :

CInteropCall MyThreadProcess = new CInteropCall();
Thread m_WorkerThread;
m_WorkerThread = new Thread(new ThreadStart(MyThreadProcess.TestMe));
m_WorkerThread.IsBackground = true;
m_WorkerThread.Name = "ThreadLow";
m_WorkerThread.Priority = System.Threading.ThreadPriority.Lowest;
m_WorkerThread.Start();


Here is the TestMe() method from the CInteropCall class which is calling
the function through an instance of a VB6 Class :
public void TestMe()
{
AX_DLL.FillAlloc_ArrayStruct_Double(3000000, ref array_struct_double);
}


The problem is :
The main UI is blocked when starting the thread, and from time to time
during the thread execution.

Solutions tested :
- adding a Sleep(1) in the inner loop of the vb6 function
It does not change a lot
- adding a DoEvents() in the vb6 function
It does not change a lot but slows down the thread execution.


The same big loop executing from a c# function does not block the main
UI (even without sleep(1)...), the UI remains very smooth.


Any idea to make an interoped vb6 activex call 'UI thread friendly' ?
(or maybe there is no way to do it...)



Regards,
Cybertof.
 
W

Willy Denoyette [MVP]

Cybertof said:
Hello,

Is there a good way to call a big time-consumming function from an
ActiveX DLL (interoped) through a thread without blocking the main UI ?

Here are the details :

I have a class CInteropCall encapsulating a call to a visual basic
ActiveX DLL function named FillAlloc_ArrayStruct_Double(), which is
allocating a big struct array an fills it using a inner loop
(for i = 1 to 300000...)

I'm calling this BigProcess through a thread from the main UI
like this :

CInteropCall MyThreadProcess = new CInteropCall();
Thread m_WorkerThread;
m_WorkerThread = new Thread(new ThreadStart(MyThreadProcess.TestMe));
m_WorkerThread.IsBackground = true;
m_WorkerThread.Name = "ThreadLow";
m_WorkerThread.Priority = System.Threading.ThreadPriority.Lowest;
m_WorkerThread.Start();


Here is the TestMe() method from the CInteropCall class which is calling
the function through an instance of a VB6 Class :
public void TestMe()
{
AX_DLL.FillAlloc_ArrayStruct_Double(3000000, ref array_struct_double);
}


The problem is :
The main UI is blocked when starting the thread, and from time to time
during the thread execution.

Solutions tested :
- adding a Sleep(1) in the inner loop of the vb6 function
It does not change a lot
- adding a DoEvents() in the vb6 function
It does not change a lot but slows down the thread execution.


The same big loop executing from a c# function does not block the main
UI (even without sleep(1)...), the UI remains very smooth.


Any idea to make an interoped vb6 activex call 'UI thread friendly' ?
(or maybe there is no way to do it...)



Regards,
Cybertof.

You should initialize your (m_WorkerThread) thread to enter an STA. The way
you do it now results in your COM object to run on the main UI thread.

...
m_WorkerThread.ApartmentState = ApartmentState.STA;
m_WorkerThread.Start();
....

Willy.
 
C

Cybertof

You should initialize your (m_WorkerThread) thread to enter an STA. The way
you do it now results in your COM object to run on the main UI thread.
..
m_WorkerThread.ApartmentState = ApartmentState.STA;
m_WorkerThread.Start();
...


Hi Willy,

That's exactly what I do not want...
I want the thread no to disturb the main UI thread at all.


Cybertof.
 
W

Willy Denoyette [MVP]

Cybertof said:
I want the thread no to disturb the main UI thread at all.


Sure, but YOU DO disturb the UI by NOT initializing the background thread to
enter an STA, note this is not the UI thread's apartment but a newly created
apartment.
If you don't initialize your backround thread to enter an STA, the AX object
will enter the UI thread's apartment and calls to methods on this object
will run on the UI thread.

Hope it's clear now.

Willy.
 
C

Cybertof

Sure, but YOU DO disturb the UI by NOT initializing the background thread to
enter an STA, note this is not the UI thread's apartment but a newly created
apartment.
If you don't initialize your backround thread to enter an STA, the AX object
will enter the UI thread's apartment and calls to methods on this object
will run on the UI thread.

Hope it's clear now.

Willy.

Thanks, it's more clear.

And should the VB6 ActiveX be compiled with
'Single Threaded' or 'Apartment Threaded' ?


Regards,
Cybertof.
 
W

Willy Denoyette [MVP]

Cybertof said:
Thanks, it's more clear.

And should the VB6 ActiveX be compiled with
'Single Threaded' or 'Apartment Threaded' ?


Regards,
Cybertof.

Apartment.

Willy.
 
C

Cybertof

Apartment.

Willy.

STA...Appartment...no changes.

I think it's not a problem of thread :

A very very long time is spent during parameter passing.

My array is passe byref, I don't understand why it takes so long time.


Is there any method to pass efficiently an array as a parm to a VB6
ActiveX DLL function ?


Regards,
Cybertof.
 
W

Willy Denoyette [MVP]

Cybertof said:
STA...Appartment...no changes.

I think it's not a problem of thread :

A very very long time is spent during parameter passing.

My array is passe byref, I don't understand why it takes so long time.


Is there any method to pass efficiently an array as a parm to a VB6
ActiveX DLL function ?


Regards,
Cybertof.

Are you sure that you create the instance of your COM object, and call the
COM method in the same thread procedure?
Failing to do so will incur thread marshaling overhead!

So the STA threading rules are simple;
- initialize your thread to enter an STA.
- call the COM object's methods on the same thread as the one that created
the object instance.

Willy.
 
C

Cybertof

Are you sure that you create the instance of your COM object, and call the
COM method in the same thread procedure?
Failing to do so will incur thread marshaling overhead!

So the STA threading rules are simple;
- initialize your thread to enter an STA.
- call the COM object's methods on the same thread as the one that created
the object instance.

Willy.

Yes, I'm sure, everything is done in the same thread.

As another example, I have removed the 2nd thread creation, and made the
call directly behing a button_click in the main UI Thread.

It's slow when passing the array to the VB6 AX Function, and returning
from the function.

It seems like the array is entirely copied instead of beeing passed
byref. (even if the ref keyword is used during the call...)


Any idea ?
 
W

Willy Denoyette [MVP]

Cybertof said:
Yes, I'm sure, everything is done in the same thread.

As another example, I have removed the 2nd thread creation, and made the
call directly behing a button_click in the main UI Thread.

It's slow when passing the array to the VB6 AX Function, and returning
from the function.

It seems like the array is entirely copied instead of beeing passed
byref. (even if the ref keyword is used during the call...)


Any idea ?


Ok, don't mix two different issues, speed and UI responsiveness. Your
initial issue was that the UI thread was blocked for the duration of the
call, the solution for this was to move this to a background thread, right?
Well, is this issue solved?
If it is, we can have a look at the speed issue, a few questions come to
mind though, how's the speed compared to a VB6 client calling into your
object? Could you post some code?

Willy.
 
C

Cybertof

Cybertof said:
Ok, don't mix two different issues, speed and UI responsiveness. Your
initial issue was that the UI thread was blocked for the duration of the
call, the solution for this was to move this to a background thread, right?
Well, is this issue solved?
If it is, we can have a look at the speed issue, a few questions come to
mind though, how's the speed compared to a VB6 client calling into your
object? Could you post some code?

Willy.

Well, the first issue is not totally solved :
Moving the call within a background thread (STA or not) gives more
responsive to the UI, but still blocks it from time to time, which does
occur only at all with the call to the ActiveX DLL Method, so I supposed
there is a thread problem with big arrays beeing passed to an ActiveX,
even if the background thread instanciates the ActiveX Class and calls
it from there.

Let's try to solve the 'Speed problem first' (easier as no background
thread is involved).

Here is some code :

*******************
VB6 ACTIVEX DLL : (in a Class named CTest)
*******************
Type VBStruct
d As Date
e As Integer
o As Double
h As Double
l As Double
c As Double
v As Long
oi As Long
End Type

Public Sub TestCall(ByRef data() As VBStruct)
MsgBox "FillArrayStruct_Double : INSIDE AX CALL" // L4
End Sub


**********************
C# Test Application : (AX_DLL is the namespace viewed from c#)
**********************
AX_DLL.CTest MyTest; // Active X DLL
AX_DLL.VBStruct[] array_struct = null;
MyTest = new AX_DLL.CTest();
startTime = DateTime.Now.Ticks;
MessageBox.Show("before alloc...");
array_struct = new AX_DLL.VBStruct[3000000]; // L1
MessageBox.Show("before ax call"); // L2
AX_DLL.TestCall(ref array_struct_double); // L3
MessageBox.Show("after ax call"); // L5




With this code, 'L1' is immediate to execute,
but, it takes quite some long time to go from
L2->L3->L4, and same long time to go through
L4->L5.

If you replace L1 with
array_struct = new AX_DLL.VBStruct[10]; // L1
everything gets very fast, no wait between calls...

The only cause of the slowness is passing a big array as a parameter.

Do you have an idea ?

Regarding the thread issue, if I put all the C# code above inside a
background STA Thread, the main UI stills blocks approximatively at the
same lines...


Regards,
Cybertof.
 
C

Cybertof

Code Update (L3 was wrong)

Please replace

AX_DLL.TestCall(ref array_struct_double); // L3

with

MyTest.TestCall(ref array_struct_double); // L3
 
W

Willy Denoyette [MVP]

Cybertof said:
Cybertof said:
Ok, don't mix two different issues, speed and UI responsiveness. Your
initial issue was that the UI thread was blocked for the duration of the
call, the solution for this was to move this to a background thread,
right?
Well, is this issue solved?
If it is, we can have a look at the speed issue, a few questions come to
mind though, how's the speed compared to a VB6 client calling into your
object? Could you post some code?

Willy.

Well, the first issue is not totally solved :
Moving the call within a background thread (STA or not) gives more
responsive to the UI, but still blocks it from time to time, which does
occur only at all with the call to the ActiveX DLL Method, so I supposed
there is a thread problem with big arrays beeing passed to an ActiveX,
even if the background thread instanciates the ActiveX Class and calls
it from there.

Let's try to solve the 'Speed problem first' (easier as no background
thread is involved).

Here is some code :

*******************
VB6 ACTIVEX DLL : (in a Class named CTest)
*******************
Type VBStruct
d As Date
e As Integer
o As Double
h As Double
l As Double
c As Double
v As Long
oi As Long
End Type

Public Sub TestCall(ByRef data() As VBStruct)
MsgBox "FillArrayStruct_Double : INSIDE AX CALL" // L4
End Sub


**********************
C# Test Application : (AX_DLL is the namespace viewed from c#)
**********************
AX_DLL.CTest MyTest; // Active X DLL
AX_DLL.VBStruct[] array_struct = null;
MyTest = new AX_DLL.CTest();
startTime = DateTime.Now.Ticks;
MessageBox.Show("before alloc...");
array_struct = new AX_DLL.VBStruct[3000000]; // L1
MessageBox.Show("before ax call"); // L2
AX_DLL.TestCall(ref array_struct_double); // L3
MessageBox.Show("after ax call"); // L5




With this code, 'L1' is immediate to execute,
but, it takes quite some long time to go from
L2->L3->L4, and same long time to go through
L4->L5.

If you replace L1 with
array_struct = new AX_DLL.VBStruct[10]; // L1
everything gets very fast, no wait between calls...

The only cause of the slowness is passing a big array as a parameter.

Do you have an idea ?

Regarding the thread issue, if I put all the C# code above inside a
background STA Thread, the main UI stills blocks approximatively at the
same lines...


Regards,
Cybertof.

Eek!!, an array of 3000000 structs (UDT in VB), I thought you were talking
about doubles. Not suprisingly it takes some time to call a method what did
you expect?

Your array takes 3M * 48 bytes ~140MB managed memory + the same amount
because it must be marshaled to unmanaged memory. That means ~300MB of
Memory taken by this single array.
If you don't have that amount free RAM - and I guess THAT'S YOUR PROBLEM,
the system will start trashing, and the time to marshal the call can tens of
seconds maybe minutes. Even if you have that amount of memory free, it will
take several seconds maybe ten to marshal.
I'm not clear why you need to pass such amount of data between managed and
unmanaged world, but this is not the best solution anyway.

Willy.
 
C

Cybertof

Eek!!, an array of 3000000 structs (UDT in VB), I thought you were >> talking
about doubles. Not suprisingly it takes some time to call a method what
did
you expect?
Your array takes 3M * 48 bytes ~140MB managed memory + the same amount
because it must be marshaled to unmanaged memory. That means ~300MB of
Memory taken by this single array.
If you don't have that amount free RAM - and I guess THAT'S YOUR > PROBLEM,
the system will start trashing, and the time to marshal the call can > tens of
seconds maybe minutes. Even if you have that amount of memory free, it
will
take several seconds maybe ten to marshal.
I'm not clear why you need to pass such amount of data between managed
and
unmanaged world, but this is not the best solution anyway.

Willy.


Willy,

Thanks for your answer.

3000000 was for exagerating the test, because I have also noticed some
slowness in smaller arrays.
Ok for the speed needed, but why does it block the main UI Thread even
when the c# is in a backgroud STA thread ?

The idea of the background thread is not to slow the UI, even with a
long task duration (like this one).

In this case, the code is launched from the main UI Thread :

Thread m_WorkerThread;
m_WorkerThread = new Thread(new ThreadStart(MyThreadProcess.TestMe));
m_WorkerThread.IsBackground = true;
m_WorkerThread.Name = "ThreadNormal";
m_WorkerThread.ApartmentState = System.Threading.ApartmentState.STA;
m_WorkerThread.Priority = System.Threading.ThreadPriority.Normal;
m_WorkerThread.Start();

(MyThreadProcess is an instance of a class making the call in my
previous code, through the TestMe method)

If I put a lower priority, it does not change, from time to time, the
main UI Thread still blocks...


Christophe.
 
W

Willy Denoyette [MVP]

Cybertof said:
Willy,

Thanks for your answer.

3000000 was for exagerating the test, because I have also noticed some
slowness in smaller arrays.
Ok for the speed needed, but why does it block the main UI Thread even
when the c# is in a backgroud STA thread ?

The idea of the background thread is not to slow the UI, even with a
long task duration (like this one).

In this case, the code is launched from the main UI Thread :

Thread m_WorkerThread;
m_WorkerThread = new Thread(new ThreadStart(MyThreadProcess.TestMe));
m_WorkerThread.IsBackground = true;
m_WorkerThread.Name = "ThreadNormal";
m_WorkerThread.ApartmentState = System.Threading.ApartmentState.STA;
m_WorkerThread.Priority = System.Threading.ThreadPriority.Normal;
m_WorkerThread.Start();

(MyThreadProcess is an instance of a class making the call in my
previous code, through the TestMe method)

If I put a lower priority, it does not change, from time to time, the
main UI Thread still blocks...


Christophe.

Again, I guess your COM object runs on the UI thread. Changing the priority
makes no sense, the CLR doesn't care about it when marshaling between
threads.
Beware if you create the instance of the COM object in MyThreadProcess
constructor, you are effectively creating an instance on the UI thread.
Please post your thread procedure or better the MyThreadProcess, the part
that creates the thread doesn't tell where you create an instance of the COM
object.
Also needed are some figures like your OS and memory size, framework
version...

Willy.
 
C

Cybertof

Beware if you create the instance of the COM object in MyThreadProcess
constructor, you are effectively creating an instance on the UI thread.
Willy,




You were right....the instanciation of the ActiveX was done in the
constructor, so in the main UI !
I have moved the instanciation to the method, and now it's better.

Last question :
How do you release memory allocated like this :
array_struct = new AX_DLL.VBStruct[3000000];

Is
array_stuct = null;
enough with the marshalling ?

I have tried to call a GC.Collet() inside the thread before it
terminates, but GC.Collet() does not seem to run into the background
thread, does it ?


Regards,
Cybertof.
 
W

Willy Denoyette [MVP]

Cybertof said:
Beware if you create the instance of the COM object in MyThreadProcess
constructor, you are effectively creating an instance on the UI thread.
Willy,




You were right....the instanciation of the ActiveX was done in the
constructor, so in the main UI !
I have moved the instanciation to the method, and now it's better.

Last question :
How do you release memory allocated like this :
array_struct = new AX_DLL.VBStruct[3000000];

Is
array_stuct = null;
enough with the marshalling ?

I have tried to call a GC.Collet() inside the thread before it
terminates, but GC.Collet() does not seem to run into the background
thread, does it ?


Regards,
Cybertof.

Yes,
array_stuct = null;
GC.Collect();
forces a GC run and frees the memory taken by the array_stuct, with such
large structs one of the legitime uses of GC.Collect(). What makes you think
it doesn't run on the background thread?
When done with the COM object your should call Marshal.ReleaseComObject(...)



Willy.
 
C

Cybertof

Yes, array_stuct = null;
GC.Collect();
forces a GC run and frees the memory taken by the array_stuct, with such
large structs one of the legitime uses of GC.Collect(). What makes you think
it doesn't run on the background thread?
When done with the COM object your should call Marshal.ReleaseComObject(...)
Willy.


Thanks for the hint about the Marshal.ReleaseComObject(...).

What made me think it does not run on the background thread is :
If I put the GC.Collect() on the big array, it slows down the main UI.
If I remove the GC.Collect(), the main UI is not affected.

Does it sound correct to you ?

GC.Collect() is a static method, not instancied within a thread, so
where does it execute ? It 'touches' all orphelin memory parts, from all
threads within the main process ?


Cybertof.
 
G

Guest

If you open up mscorlib.dll with ILDASM and look at GC.Collect() you will see
it eventually gets to the private method GC.nativeCollectGeneration(). You
will see that this is marked with the pseudoattibute "internalcall" and that
the actual code of it is blank. This is because the Collection occures within
the .NET Runtime itself and not anywhere in your code.

However, it still poses a bigger threat. Since GC works with "generations",
whenever Garbage Collection occures, the entire Runtime for that application
will pause during collection, reguardless of threads. I would recommend
setting what you don't need as null, but don't call collection. When GC does
happen on it's own and it sees that it is null, it won't hesitate to collect
it.

Another factor about collection is resurrection. Resurrection is exactly as
it sounds. When an application is no longer accessing a live object, the
garbage collector considers the object to be dead. However, if the object
requires finalization, the object is considered live again until it is
actually finalized, and then it is permanently dead. In other words, an
object requiring finalization dies, lives, and then dies again. When the
object is going to be finalized, avoid multiple resurections, or worse, a
loop where the object is called for finalization, resurrected, and then set
for finalization again, then finalized, so it get's stuck.

Be careful when working with static or global objects as well. Something
like this would hurt:

public class BaseObj {

~BaseObj() {
Application.ObjHolder = this;
}
}

class Application {
public static Object ObjHolder; // Defaults to null
....
}

Note about how GC works in the Framework, objects that are 20,000 bytes or
larger are placed on a special heap. This is transparent so the developer nor
the actual code itself will know it. As far as it is concerned, it is one
heap. The reason for this is the special heap is never compacted. Shifting
20,000 bytes or more down the heap will require too much CPU time.

Generally I would recommend not calling collection on your own unless you
have very good reasons.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top