# VBForums CodeBank > CodeBank - Visual Basic 6 and earlier >  VB Multithread Library (Generic Multithreading)

## Krool

This is a Ax-DLL (VBMThread11.DLL) for generic multithreading in VB6.

Current version: 1.1.2

There is only one public creatable component, the Thread class.
The Thread class is interface driven and once created the new thread's main function is fired in the BackgroundProcedure event.
In this event the background work will be done, as it will not lock or freeze the App.
But attention is required as showing Forms will crash VB.
Therefore in the BackgroundProcedure event is a param StatusCallback. (IThreadStatusCallback class)
By this you can fire an synchronous callback. The background thread will be suspended, the method request is marshaled to the main thread.
On this StatusCallback event you can safely show forms or report progress.
It is recommended to access project objects (classes, controls) only in the StatusCallback event.

Do not debug or use 'App.Path' in the BackgroundProcedure event, doing so will cause a crash. (Unless the DebugMode property was set to True)

The source code of the project can also be viewed on GitHub.

Registration-Free (Side-by-side) solution for VBMThread11.DLL can be found here.

*Notes:*
- P-Code will fail. Native compilation is necessary.

*List of revisions:*


```
29-Apr-2017
- The Thread class is not event driven anymore. All "events" are fired now trough an IThread interface.
- Included mandatory Owner parameter in the Create method. That owner will receive the IThread events.
  Also included an optional Key parameter that helps to identify multiple threads on the same owner.
- Renamed event 'AsyncProcedure' to 'BackgroundProcedure' and 'SyncCallback' to 'StatusCallback'.
- Included event 'Complete' that fires after the thread ran to completion or was canceled.
- Optional Wait parameter included in the Terminate method.
- Included the ThreadData class that will be passed along the BackgroundProcedure and Complete event.
- Included Cancel method. This method sets the CancellationPending property to True in the ThreadData.
  How fast the cancellation is done depends on the code in the background procedure and if the developer respects the cancellation request.
- Included DebugMode property. If set to True the background thread runs synchronous to the main thread.
  This does allow to safely set breakpoints and perform debugging in the IDE.
17-Jun-2016
- Fixed a bug in the Suspended property.
15-Jun-2016
- First release.
```

Attachment: VBMThread11 Ax-DLL project and a demo project.

----------


## DEXWERX

> This is a Ax-DLL (VBMThread10.DLL) for generic multithreading in VB6.
> 
> I know that there are plenty of libraries out there providing exactly this.
> However, I want to share this approach and want to demonstrate how to use it.
> 
> Registration-Free (Side-by-side) is not working on this Ax-DLL. It must be registered normally in order to function.


No libraries are out there providing a barebones minimal framework that is open source...
you beat me to it! Very nice. You've done a great many services to the community.

----------


## The trick

Great, but it is the very restricted multithreading, because you can't work with the project specified objects classes/forms/controls/etc. I described the similar approach here.

----------


## Krool

> Great, but it is the very restricted multithreading, because you can't work with the project specified objects classes/forms/controls/etc.


When you want to work with project specified objects (classes/forms/controls/etc.) you need to make a SyncCallback.Raise in the AsyncProcedure event.
In this SyncCallback event you can do everything safely as normal. (it is running on the original main thread)
In the AsyncProcedure event itself you can work with objects which you do create within the procedure, e.g. CreateObject() or simple project classes.

Example:


```
Private Sub BackWorker_AsyncProcedure(ByVal SyncCallback As VBMThread10.IThreadSyncCallback)
' ...
SyncCallback.Raise
End Sub

Private Sub BackWorker_SyncCallback(Argument As Variant)
CommonDialogPageSetup.ShowPageSetup ' Project object variable access
End Sub
```

So I think this approach makes it quite flexible.
And it was my goal to make a generic solution which is also simple.

----------


## DEXWERX

> Great, but it is the very restricted multithreading, because you can't work with the project specified objects classes/forms/controls/etc. I described the similar approach here.





> So I think this approach makes it quite flexible.
> And it was my goal to make a generic solution which is also simple.


It's a very simple asyncronous framework.
It's simple enough for someone to incorporate Trick's methods, as well as add more flexibility or complexity.

It would be nice for it to be single threaded for testing / IDE debugging, although again that can be added outside of the framework.

----------


## The trick

> It's a very simple asyncronous framework.


Yes. Also, it's very useful for the multithreading newbies and professionals too because it doesn't require the big efforts for understanding. Everything is very simple - Create/Asynch/Synch. Of course there is some issues with parameters but at first time it doesn't matter.



> It's simple enough for someone to incorporate Trick's methods, as well as add more flexibility or complexity.


I would like to note the few details. The code should have the message loop in order to support the marshaling. For example if a project doesn't contain forms you have to loop message cycle (DoEvents, for example, but it loads a thread it's better use GetMessage/TranslateMessage/DispatchMessage functions). You should wait for the thread termination as well. Optionally, as an user of your library i would want to have the ability of passing a parameter to a thread.



> It would be nice for it to be single threaded for testing / IDE debugging, although again that can be added outside of the framework.


The *Krool* approach is quite stable in your example, but it'll crash anyway in IDE. All my methods doesn't work in IDE (except EXE multithreading that works in the main thread). I absolutely have no time to translate my examples to English and public the new projects. In short, that example uses the precompiled DLL and creates the object in the new thread. You have the ability to call a method synchronously and asynchronously and receive the event from this object.

----------


## ChenLin

Can this be used in the sql server database query?

----------


## Krool

Update released.
Minor bugfix in the Suspended property.
The DLL in /Bin have been recompiled with binary compatibility.




> Optionally, as an user of your library i would want to have the ability of passing a parameter to a thread.


You could access the projects variables within the AsyncProcedure event and use this as a passing parameter. But, of course a Param could be included out of the event itself.




> Can this be used in the sql server database query?


If you create the objects within the AsyncProcedure event it should be possible.

----------


## Kunical

Good work.

Could this DLL be merged with an ActiveX EXE ?

----------


## DEXWERX

> Good work.
> 
> Could this DLL be merged with an ActiveX EXE ?


Why would you need this in an ActiveX EXE, considering an ActiveX EXE can use VB6's standard multithreading methods?
(setting the Threading Model)

----------


## Krool

Here is a solution to use the VBMThread11.DLL Registration-Free. (Side-by-side)
Keep in mind that this technology needs at minimum Windows XP SP2 or Windows Server 2003.

Big thanks goes to 'the trick' for the solution with "comInterfaceExternalProxyStub" in the manifest.

*Tutorial:*
The "Development" machine needs to register VBMThread11.DLL as usual and use the components for e.g. in a Std-EXE project.
The source project needs to include the Side-by-side resources. (see below)
Then on the "End user" machine you only need the VBMThread11.DLL and the .exe (Std-EXE project) on the same folder.
It will work then without any registration.

The source code of "VBMThread11SideBySide.res" is:



```
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
   <file name="VBMThread11.DLL">
      <typelib tlbid="{1A02D9E1-9609-40B3-8AFE-727D8A144707}" version="1.0" flags="" helpdir="" />
      <comClass clsid="{AAE10FA6-5E20-4FA7-9649-678DC3971353}" tlbid="{1A02D9E1-9609-40B3-8AFE-727D8A144707}" threadingModel="Apartment" progid="VBMThread11.Thread" description="" />
   </file>
   <comInterfaceExternalProxyStub
      name = "_Thread"
      iid="{71154E34-A914-4E30-A4BA-5CE94A6D9C46}"
      proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
      baseInterface="{00000000-0000-0000-C000-000000000046}"
      tlbid="{1A02D9E1-9609-40B3-8AFE-727D8A144707}">
   </comInterfaceExternalProxyStub>
   <comInterfaceExternalProxyStub
      name = "__Thread"
      iid="{403717A2-9E4E-4FEC-B77A-62DC68B9C294}"
      proxyStubClsid32="{00020420-0000-0000-C000-000000000046}"
      baseInterface="{00000000-0000-0000-C000-000000000046}"
      tlbid="{1A02D9E1-9609-40B3-8AFE-727D8A144707}">
   </comInterfaceExternalProxyStub>
   <comInterfaceExternalProxyStub
      name = "_ThreadData"
      iid="{55A8AC17-7DB5-468D-93BD-86E8B97C53A5}"
      proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
      baseInterface="{00000000-0000-0000-C000-000000000046}"
      tlbid="{1A02D9E1-9609-40B3-8AFE-727D8A144707}">
   </comInterfaceExternalProxyStub>
   <comInterfaceExternalProxyStub
      name = "_IThread"
      iid="{68F61D7E-21CD-41E6-AFEC-C3820C6743D5}"
      proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
      baseInterface="{00000000-0000-0000-C000-000000000046}"
      tlbid="{1A02D9E1-9609-40B3-8AFE-727D8A144707}">
   </comInterfaceExternalProxyStub>
   <comInterfaceExternalProxyStub
      name = "_IThreadStatusCallback"
      iid="{09256C4A-7A89-40F6-BBEE-3D5445942C4A}"
      proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
      baseInterface="{00000000-0000-0000-C000-000000000046}"
      tlbid="{1A02D9E1-9609-40B3-8AFE-727D8A144707}">
   </comInterfaceExternalProxyStub>
</assembly>
```

In the attachment I have added the same as a compiled resource.

----------


## loquat

Dear Krool, how can i use the res file?
i have try to find the original post about comInterfaceExternalProxyStub, but found nothing.
can you indicate it

----------


## Krool

It is also possible to add more complexity in the interaction between the AsyncProcedure and SyncCallback events.
So one would create a project class called cParams, which could just contain two properties named 'Command' and 'Value'.



```
Private Sub BackWorker_AsyncProcedure(ByVal SyncCallback As VBMThread10.IThreadSyncCallback)
Dim Params As cParams
Set Params = New cParams
Params.Command = "Do that and this"
Params.Value = 150
SyncCallback.Raise Params
End Sub

Private Sub BackWorker_SyncCallback(Argument As Variant)
If Argument.Command = "Do that and this" Then MsgBox Argument.Value
End Sub
```

Conclusion:
In general there seems to be no issue to create or even access classes from the project, at least for simple classes, within the AsyncProcedure event.

----------


## DEXWERX

It seems like the runtime has no issue automatically marshalling our interfaces between threads?

----------


## Eduardo-

It seems very useful.
Thanks for the sample project.

I would be even better if the objects of the main thread could be used, something like SyncCallback.Marshall(SomeRecordset).MoveFirst...
But perhaps it's too difficult to do (or am I saying nonsense?).

----------


## DEXWERX

> It seems very useful.
> Thanks for the sample project.
> 
> I would be even better if the objects of the main thread could be used, something like SyncCallback.Marshall(SomeRecordset).MoveFirst...
> But perhaps it's too difficult to do (or am I saying nonsense?).


You can marshall interfaces to the async procedure yourself if you really need to, but using the SyncCallback is way easier.
Also hopefully the example you posted wasnt a real world usage... 

using a Recordset on the main thread from another thread - completely defeats the purpose of using another thread.

----------


## Eduardo-

> You can marshall interfaces to the async procedure yourself if you really need to, but using the SyncCallback is way easier.
> Also hopefully the example you posted wasnt a real world usage...


How do you do that?
How do you send an object from the SyncCallback to be used in the async procedure (the one that executes in the new thread)?




> using a Recordset on the main thread from another thread - completely defeats the purpose of using another thread.


As I understand it, may be or may be not.
If I would be doing intensive use of the recorset you are right, but if I only needed to make some consult (and the main task is another one) not, I think.

----------


## DEXWERX

> How do you do that?
> How do you send an object from the SyncCallback to be used in the async procedure (the one that executes in the new thread)?


https://msdn.microsoft.com/en-us/lib...=vs.85%29.aspx

I guess I can try and put together a demo, but the Threading DLL source shows how to manually martial a "Thread" interface from the main thread onto the new thread.




> As I understand it, may be or may be not.
> If I would be doing intensive use of the recorset you are right, but if I only needed to make some consult (and the main task is another one) not, I think.


That's correct, just trying to be clear. MoveFirst implied you're going to use a recordset in a loop. *shrugs*

----------


## DEXWERX

Funny story... You can use COM's automatic martialling.



```
Option Explicit

Private WithEvents t As Thread
Private m_Rec As ADODB.Recordset

Private Sub Form_Click()
    t.Create
End Sub

Private Sub Form_Load()
    'Create In Memory Recordset
    Set m_Rec = New ADODB.Recordset
    With m_Rec
        .Fields.Append "Name", adVarChar, 10, adFldMayBeNull
        .CursorType = adOpenKeyset
        .CursorLocation = adUseClient
        .LockType = adLockPessimistic
        .Open
        Dim Ind As Long
        For Ind = 1 To 5
            .AddNew
            .Fields(0) = "Name " & Ind
            .Update
            .MoveNext
        Next
    End With
    
    Set t = New Thread
End Sub

Private Sub Form_Unload(Cancel As Integer)
    If t.hThread <> 0 Then
        Cancel = True
        MsgBox "Thread is not complete."
        Exit Sub
    End If
End Sub

Private Sub t_AsyncProcedure(ByVal SyncCallback As VBMThread10.IThreadSyncCallback)
    Dim a, MyRec As ADODB.Recordset
    SyncCallback.Raise a
    Set MyRec = a
    MyRec.MoveFirst
    Do Until MyRec.EOF
        'OutputDebugString MyRec.Fields(0)
        SyncCallback.Raise MyRec.Fields(0).Value
        MyRec.MoveNext
    Loop
End Sub

Private Sub t_SyncCallback(Argument As Variant)
    If VarType(Argument) = vbString Then
        MsgBox Argument
    Else
        Set Argument = m_Rec
    End If
End Sub
```

----------


## Eduardo-

Ah, that's great, then... does the Argument parameter marshall any COM object passed between the procedures?
(I still didn't study the DLL source code)

Another question: only one object at a time can be marshalled?

----------


## DEXWERX

> Ah, that's great, then... does the Argument parameter marshall any COM object passed between the procedures?
> (I still didn't study the DLL source code)
> 
> Another question: only one object at a time can be marshalled?


It doesn't seem like private objects. But you can automatically Marshall private objects as IDispatch (Object).
And then latebound calls work.



```
Private Sub t_AsyncProcedure(ByVal SyncCallback As VBMThread10.IThreadSyncCallback)
    Dim a, f, MyRec As ADODB.Recordset, MyForm As Object 'Form1 is private to main thread...
    SyncCallback.Raise a
    Set MyRec = a
    f = "Form"
    SyncCallback.Raise f
    Set MyForm = f
    MyForm.Caption = "Caption Set From the other thread!"
    MyRec.MoveFirst
    Do Until MyRec.EOF
        SyncCallback.Raise MyRec.Fields(0).Value & " - " '& WINAPI.GetCurrentThreadId ' API's called on other thread using a typelib.
        MyRec.MoveNext
    Loop
End Sub

Private Sub t_SyncCallback(Argument As Variant)
    If VarType(Argument) = vbString Then
        Select Case Argument
        Case "Form"
            Dim Obj As Object
            Set Obj = Me
            Set Argument = Obj
        Case Else
            MsgBox "Main Thread: " & App.ThreadID() & " " & Argument
        End Select
    Else
        Set Argument = m_Rec
    End If
End Sub
```

or an Array of Variant or Object. Seems to work.



```
Private Sub t_AsyncProcedure(ByVal SyncCallback As VBMThread10.IThreadSyncCallback)
    Dim a, MyRec As ADODB.Recordset, MyForm As Object 'Form1
    SyncCallback.Raise a
    Set MyForm = a(0)
    Set MyRec = a(1)
    MyForm.Caption = "Caption Set From the other thread!"
    MyRec.MoveFirst
    Do Until MyRec.EOF
        SyncCallback.Raise MyRec.Fields(0).Value & " - " & WINAPI.GetCurrentThreadId
        MyRec.MoveNext
    Loop
End Sub

Private Sub t_SyncCallback(Argument As Variant)
    Dim Args(1) ' As Object ' Or As Variant
    Dim Obj As Object: Set Obj = Me
    
    If VarType(Argument) = vbString Then
        MsgBox "Main Thread: " & App.ThreadID() & " " & Argument
    Else
        Set Args(0) = Obj
        Set Args(1) = m_Rec
        Argument = Args
    End If
End Sub
```

----------


## Eduardo-

Great. That is very useful. Thanks a lot.

----------


## LaVolpe

Thanx for the project. Unfortunately, and maybe nothing you can do about it, it isn't very safe in the IDE. For example, I tried to terminate any active threads in form unload while the progress bar window was running, but it crashed. When compiled, no crash. The simple example looked like this:


```
    Me.Hide
    If BackgroundWorker.hThread Then
        Do Until BackgroundWorker.Terminate = True
            Sleep 100
        Loop
    End If
    If FormWorker.hThread Then
        Do Until FormWorker.Terminate = True
            Sleep 100
        Loop
    End If
```

Maybe your library can provide an optional synchronous Terminate event? Returning only when thread successfully terminated? Kinda like:


```
Public Function Terminate(Optional AsSynchronous As Boolean = False) As Boolean
```

Edited: If you consider the above request, maybe consider adding a Synch option to other functions, like Suspend, etc. Doing so should negate the main thread from having to loop, if necessary, while waiting for the action to take effect

----------


## LaVolpe

Hoping you will consider the synchronous functions described previously. In any case, I'd like to say this project feels pretty good and would like to provide some positive feedback after a few preliminary tests I've done:

Overview the way I understand it: The thread is purely a background thread. After the AsyncProcedure callback returns to the Thread class, the thread is automatically terminated. In other words, when the main thread's final SyncCallback routine exits, consider the thread dead. Final? Yep, as shown in posts above, you may have designed the AsyncProcedure to have multiple SyncCallback calls.

Task 1: I was thinking of using multi-threading for GDI+ loaded GIFs and other animation. The idea was to pass the thread a GDI+ image handle and frame number. The thread would prepare the frame, render to a DC with any special rendering options. The main thread would draw the frame when either the thread finished or the previous frame's duration occurred, whichever occurs latest. Afterwards, another thread created and next frame is processed. This appears to work pretty well. Main thread is not tied up during the processing of the frame. If multiple images are being animated; this is noticeable. Very good.

Task 2: Have GDI+ load an image within the background thread and return the image handle to the main thread when done. For very large images, the main thread is not tied up. Again, very good.

The key point here, for those that mess with GDI+, is that it appears the image handle can be passed between threads without crashing. I wasn't positive about that. I've tested creating the GDI+ image in the background thread and processing it in the main thread and vice versa. In either scenario the image handle was created and processed in different threads. Of course, some care needs to be taken. Accessing the image handle simultaneously in both threads would likely end in unexpected results, at best, crashes at worst; especially with multi-image formats like GIF/TIFF. More testing needed.

----------


## Krool

> Hoping you will consider the synchronous functions described previously. In any case, I'd like to say this project feels pretty good and would like to provide some positive feedback after a few preliminary tests I've done:
> 
> Overview the way I understand it: The thread is purely a background thread. After the AsyncProcedure callback returns to the Thread class, the thread is automatically terminated. In other words, when the main thread's final SyncCallback routine exits, consider the thread dead. Final? Yep, as shown in posts above, you may have designed the AsyncProcedure to have multiple SyncCallback calls.


Thanks for testing. It seems like a 1.1 version is overdue.
I would also like to add a 'Argument2' in the SyncCallback to enable more flexibility out of the box without extra tricks, like array etc.

For the sync Terminate function. So like a "Public Function Terminate(Optional ByVal Wait As Boolean)"
When passing True at call the function would return only when the thread is finally terminated?

About your understanding overview. Yes, correct. If the AsyncProcedure returns the task is finished and thread is terminated automatically.
The SyncCallback can be called multiple times or none at all. That's not a condition to have it called at least once.

----------


## LaVolpe

> I would also like to add a 'Argument2' in the SyncCallback to enable more flexibility out of the box without extra tricks, like array etc.


Still may not be enough for many calls. In my GDI+ example, I may want to pass an image handle, frame number, color attributes handle and more for processing within the background thread. Dex showed how an array can be assigned to the variant argument for passing multiple values & I feel it's a good workaround. But at least two parameters would be better, something as you described in your post #13. This way SyncCallback can be better written by the user by testing the "command" parameter passed from AsyncProcedure.

Regarding raised events. Within VB, these are blocked when a modal form/window (i.e., MsgBox) is displayed within the IDE. An implementation vs withevents would solve that issue. Is this a real concern? Yes, possibly. In my GDI+ example, if the thread creates the image handle then calls the SyncCallback to pass the handle and the event is blocked, the main thread doesn't know about the handle and cannot free/release it. Of course, a workaround would be to require the SyncCallback to pass a flag within the arguments to determine if the call was received from AsyncProcedure. However, instead of testing every call, an implementation would be a better solution. Example of event blocking, the debug statement is not executed...


```
Private Sub Command1_Click()
   BackgroundWorker.Create
   MsgBox "Block Events"
End Sub

Private Sub BackgroundWorker_AsyncProcedure(ByVal SyncCallback As VBMThread10.IThreadSyncCallback)
    SyncCallback.Raise 0
End Sub

Private Sub BackgroundWorker_SyncCallback(Argument As Variant)
   ' ^^^ This is blocked when modal window exists
   Debug.Print "syncCallback received'
End Sub
```




> When passing True at call the function would return only when the thread is finally terminated?


Yes, that's the idea & similar for Suspend

----------


## Krool

> Regarding raised events. Within VB, these are blocked when a modal form/window (i.e., MsgBox) is displayed within the IDE. An implementation vs withevents would solve that issue. Is this a real concern? Yes, possibly. In my GDI+ example, if the thread creates the image handle then calls the SyncCallback to pass the handle and the event is blocked, the main thread doesn't know about the handle and cannot free/release it. Of course, a workaround would be to require the SyncCallback to pass a flag within the arguments to determine if the call was received from AsyncProcedure. However, instead of testing every call, an implementation would be a better solution. Example of event blocking, the debug statement is not executed...
> 
> 
> ```
> Private Sub Command1_Click()
>    BackgroundWorker.Create
>    MsgBox "Block Events"
> End Sub
> 
> ...


I could imagine the following solution. To have either way.

Include in the Thread class a 'CustomIThreadSyncCallback' property. When this is Nothing fire normal event, else use the interface.

So in Form you include IThreadSyncCallback and you then would do a "Set Thread.CustomIThreadSyncCallback = Me", where 'Me' is the Form.

----------


## LaVolpe

That may be a workaround. However, if you are going to update this library, suggest using an Interface instead of raising events. I believe that is a better and cleaner overall solution. The form/uc/class that is creating the thread can pass itself to the thread's Create method and the Terminate method can release the reference. The raiseevent calls would be replaced by calling the implementation instead.

----------


## Krool

But the AsyncProcedure can stay as an event?
So in the Create method there would be an additional non-optional IThreadSyncCallback parameter by which 'Me' will be normally passed.

Or else a second interface is needed for the Async part.

----------


## LaVolpe

Krool, I don't have the deep understanding of multi-threading that you do. Is there reason why you need to RaiseEvent vs. an Implementation? The idea I was suggesting is something like this. It is rough, not polished:


```
' new Interface to be used by customers. Let's say it's named: IThread
Public Sub AsyncProcedure(ByVal SyncCallback As IThreadSyncCallback): End Sub
Public Sub SyncCallback(ByRef Argument As Variant): End Sub

 ' the Thread.Cls
add this: Private m_Consumer As IThread
change Create to: Public Function Create(Owner As IThread) As Boolean
   Set m_Consumer = Owner ' validate that Nothing was not passed

 ' then instead of raising the two events, this:
         m_Consumer.SyncCallback Argument
and   m_Consumer.AsyncProcedure SyncCallback

add this in the Terminate event:
   Set m_Consumer = Nothing
```

The caller would use: Implements IThread
and then initiate with: BackgroundWorker.Create Me

Edited: Many of the changes you make may break binary compatibility, unless you create new function names, i.e., CreateEx instead of modifying the Create function signature.

----------


## Krool

Good idea. Thought also that new interface is needed.
But ofcourse the new 'IThread' can provide both "events".
Will check this soon.

Also I might include an optional 'ByVal CustData As Long' in the Create method which will be passed along AsyncProcedure.

----------


## LaVolpe

> Also I might include an optional 'ByVal CustData As Long' in the Create method which will be passed along AsyncProcedure.


Good idea. This way if the AsyncProcedure has no other reason to call SyncCallback, it doesn't need to and the task can be completely handled in the background thread as long as the user has a way to confirm that the task succeeded (exit code or some other method). Or Maybe the AsyncProcedure calls SyncCallback to pass success or failure?

----------


## Krool

> Edited: Many of the changes you make may break binary compatibility, unless you create new function names, i.e., CreateEx instead of modifying the Create function signature.


I will need to break binary compatibility anyhow as I will remove some events, thus 1.1 version.
Ofcourse then I need to create a new manifest resource. But it's cleaner that way instead of bloating everything up.

----------


## LaVolpe

One more, minor suggestion:
Change AsyncProcedure name to BkgThreadProcedure
Change SyncCallback name to MainThreadProcedure

Or some other names that may be more intuitive/descriptive of what is occurring in the event. It may be more helpful to users to better understand which event belongs to the multi-thread and which belongs to the main thread. Just a thought.

----------


## Eduardo-

With the events interface it is possible to add several new threads code to the same form (or same instance of a class module), but with the version using implements, for also enabling that possibility, it would have to have an additional parameter to identify the thread, I think.

Edit: it can be the thread's name, so the code to create the thread, intead of:



```
BackgroundWorker.Create
```

it could be:



```
CreateThread "BackgroundWorker"
```

----------


## LaVolpe

> With the events interface it is possible to add several new threads code to the same form (or same instance of a class module), but with the version using implements, for also enabling that possibility, it would have to have an additional parameter to identify the thread, I think.


Good point. There are at least 2 ways of handling that

1. As you suggest, Krool could add a Key parameter to the Create event and that value passed with each implemented method. The user can Select Case / If:Else on that key. Many solutions use a key or user param to identify which instance is being used.

2. Create a class for each type of background thread and have the class Implement the interface, then pass the class instance instead of the form instance.

----------


## Krool

I also have the idea of bringing up a 'ThreadCancelBoolean' class which has a Value Boolean property (default property) that is passed along 'AsyncProcedure'.
Kind of "AsyncProcedure(SyncCallback As IThreadSyncCallback, Cancel As ThreadCancelBoolean)
In the Thread class there would be a Cancel method to make a 'soft terminate'.
So in the AsyncProcedure there could be an "If Cancel = True" be included by the coder to bring the AsyncProcedure faster to it's end.  :Big Grin: 

Concerning the description. I do find Async.. and Sync.. already as self-explained? Or?

----------


## LaVolpe

> Concerning the description. I do find Async.. and Sync.. already as self-explained? Or?


Maybe I am too dense  :Smilie:  I didn't pick that up right away. Your control, your rules. Something we can all live by.

----------


## Eduardo-

If passing a string is not much overhead, I suggest to identify each threads with a name.



```
Implements IThreads

Private Sub Form_Load()
    CreateThread "BackgroundWorker1"
    CreateThread "BackgroundWorker2"
End Sub

Private Sub IThread_AsyncProcedure(TheadName As String, asSyncCallback As IThreadSyncCallback, Cancel As ThreadCancelBoolean)

End Sub

Private Sub IThread_SyncCallback(TheadName As String, Argument1 As Variant, Argument2 As Variant)

End Sub
```

And about the names, I vote for Lavolpe's suggestion of BkgThreadProcedure and MainThreadProcedure. I think they are more intuitive.

----------


## Eduardo-

> Your control, your rules. Something we can all live by.


Of course.

----------


## LaVolpe

And Krool, another suggestion, but I haven't thought this one out completely.

Scenario: After last SyncCallback event, I may want to trigger main thread events/actions but want the thread released immediately. If I call those actions from the SyncCallback event, thread is help up (synchronous after all). 

Solutions? Maybe a terminate event can be raised after the thread is killed. But additionally, passing a value to that event that is set inside the SyncCallback event. Maybe your ThreadCancelBoolean idea can have another property (variant, user defined)? And that value is passed to the terminate event? As a workaround, I was considering a timer event or PostMessage type event (asynchronous). However, a terminate event with optional value would be ideal I think.

----------


## Krool

> If passing a string is not much overhead, I suggest to identify each threads with a name.
> 
> 
> 
> ```
> Implements IThread
> 
> Private Sub Form_Load()
>     CreateThread "BackgroundWorker1"
> ...


What is 'CreateThread'. A public module function?
I think the "Thread.Create Me, 'Key123' " is the way I prefer.

Also if "Thread.Create Nothing" is passed it could be still event driven? Thus the "Owner As IThread" could be optional?
So it would be possible to have either way. Everyone can decide to provide a Owner or use the Events. Of course the 'Key' param would be valid in both scenarios.

----------


## Eduardo-

> What is 'CreateThread'. A public module function?
> I think the "Thread.Create Me, 'Key123' " is the way I prefer.


OK.




> Also if "Thread.Create Nothing" is passed it could be still event driven? Thus the "Owner As IThread" could be optional?
> So it would be possible to have either way. Everyone can decide to provide a Owner or use the Events. Of course the 'Key' param would be valid in both scenarios.


Good idea to have both interfaces, then I would make the name parameter optional and also the Owner.

----------


## Eduardo-

> What is 'CreateThread'. A public module function?


Yes, the idea was to have that function in a GlobalMultiuse class of the component, and that it returns a Thread object.

If the developer doesn't need to have a reference to the Thread object, he can just call the function without holding any reference to the returned object.

----------


## Krool

I got the IThread to work now in my new 1.1 version. (work in progress)


```
Public Sub AsyncProcedure(ByVal Key As String, ByVal SyncCallback As IThreadSyncCallback)
End Sub

Public Sub SyncCallback(ByVal Key As String, ByRef Argument1 As Variant, ByRef Argument2 As Variant)
End Sub
```

The Create function looks now as following:


```
Public Function Create(Optional ByVal Owner As IThread, Optional ByVal Key As String) As Boolean
```

When Owner is Nothing, then the normal Events of the Thread class will be raised. The Key param is ignored in this case, only used when Owner is passed.

Concerning the Terminate function, I have included an Optional Wait parameter. (Default is False)


```
    If Wait = False Then
        If TerminateThread(PropThreadHandle, dwExitCode) <> 0 Then Me.FClear
    Else
        While TerminateThread(PropThreadHandle, dwExitCode) = 0
            Sleep 200
        Wend
        Me.FClear
    End If
```

In my testings there is no difference if Wait is True or not, the success is immediately.
However, I have read some documentations that TerminateThread just kicks in a scheduler which does the task, so there can be some delay in it...

About the namings (Events/IThread interfaces) I am still struggling with myself.  :Stick Out Tongue:

----------


## LaVolpe

Question. MSDN says TerminateThread is a last resort, similar to End in the exe. Should ExitThread be used instead? You probably already tested that, but I am curious as to why TerminateThread was used vs ExitThread? 

Additionally, do you have the GetExitCodeThread and TerminateThread calls in the current class Terminate event in the wrong order? I ask because GetExitCodeThread would return the exit code passed to TerminateThread/ThreadExit which indicates those would be called first. Additionally, if GetExitCodeThread returns "STILL_ACTIVE" then thread has not yet terminated.



> GetExitCodeThread return valueThis function returns immediately. If the specified thread has not terminated and the function succeeds, the status returned is STILL_ACTIVE. If the thread has terminated and the function succeeds, the status returned is one of the following values:
>     The exit value specified in the ExitThread or TerminateThread function.
>     The return value from the thread function.
>     The exit value of the thread's process.


Edited: Maybe Terminate Event is a bad idea after all? Why? It's like calling End while subclassing. 
Also, I was trying to figure out how to get the AsyncProcedure to prematurely abort and forgot about your example using a public form-level variable within the AsyncProcedure event. However, I still could not unload the form while the threads were active. So needed to find a way to test when they were terminated. As mentioned several posts ago, using a DoEvents loop testing for Terminate event to return true resulted in crashes. So I set the global variable and tried a DoEvents loop testing to see when the thread's ID became zero and crashed again. The solution that did work, was to cancel the unloading, set the public variable and activate a timer. In the timer event, I just waited until the thread ID became zero then triggered Form_Unload. That worked every time without crashing. 


```
Private Sub Form_Unload(...)
    If BackgroundWorker.ThreadID <> 0 Then
        gAbortAllThreads = True ' tested in the AsyncProcedure & aborts that routine early if set
        Timer1.Enabled = True  ' 10 ms timer
        Cancel = 1
    Else
        .... do clean up
    End If
End Sub
Private Sub Timer1_Timer()
    If BackgroundWorker.ThreadID = 0 Then
        Timer1.Enabled = False
        Unload Me
    End If
End Sub
```

So now, I'm questioning myself whether DoEvents can crash the IDE while multi-threading.

If you missed it, can you review post #41 also (only if you are still going to provide a Terminate event)?

Regarding names of the raised events and those of the interface, hmmm...  Can you not call them the same? They will be on different classes (thread.cls and IThread.cls). That is a guess, never designed a class that would do both.

----------


## Eduardo-

About terminating a thread, from the client point of view, wouldn't it be that you simply have to exit the Asynch procedure and the thread would terminate naturally?

----------


## LaVolpe

> About terminating a thread, from the client point of view, wouldn't it be that you simply have to exit the Asynch procedure and the thread would terminate naturally?


Yes, but the question is how to do that prematurely. How to abort that routine without causing a crash in IDE. And then the thread may not be terminated immediately, but slightly delayed which can crash the IDE if unloading before that happens. But if no SyncCallback being used, how to abort AsyncProcedure early?

Edit: Answer... public variable at form level. Appears that can be placed in the AsyncProcedure and tested. I missed that point. Krool's sample project includes such a case using the DialogClosed public variable. I edited my previous post to reflect this. The timer solution, for unloading form while threads are active, worked quite well. Just FYI. Doing so, the timer fired from 3 to 9 times before the thread was terminated. This count is likely partly due to the Sleep calls in the AsyncProcedure.

----------


## Eduardo-

> Yes, but the question is how to do that prematurely. How to abort that routine without causing a crash in IDE. And then the thread may not be terminated immediately, but slightly delayed which can crash the IDE if unloading before that happens. But if no SyncCallback being used, how to abort AsyncProcedure early?
> 
> Edit: Answer... public variable at form level. Appears that can be placed in the AsyncProcedure and tested. I missed that point. Krool's sample project includes such a case using the DialogClosed public variable. I edited my previous post to reflect this. The timer solution, for unloading form while threads are active, worked quite well. Just FYI. Doing so, the timer fired from 3 to 9 times before the thread was terminated. This count is likely partly due to the Sleep calls in the AsyncProcedure.


A timer or a loop with Sleep.
How do you know when all threads (or each one separately) have really finished?

----------


## Eduardo-

I'm thinking that in the case of the implementation interface, it shouldn't be a problem, because the component holds a reference to the caller object (form or class), and if the component doesn't release this reference until after the background thread ends, the main thread won't finish until then.

Am I right?

----------


## Krool

> Question. MSDN says TerminateThread is a last resort, similar to End in the exe. Should ExitThread be used instead? You probably already tested that, but I am curious as to why TerminateThread was used vs ExitThread? 
> 
> Additionally, do you have the GetExitCodeThread and TerminateThread calls in the current class Terminate event in the wrong order? I ask because GetExitCodeThread would return the exit code passed to TerminateThread/ThreadExit which indicates those would be called first. Additionally, if GetExitCodeThread returns "STILL_ACTIVE" then thread has not yet terminated.
> 
> Edited: Maybe Terminate Event is a bad idea after all? Why? It's like calling End while subclassing.


ExitThread can only be called from the AsyncProcedure itself and not from the "outside". Ofcourse we have the access to the AsyncProcedure that's why I already plan to make a "soft terminate" with a Cancel Boolean.

About GetExitCodeThread. According to TerminateThread MSDN it is a _In variable and the value must be passed by GetExitCodeThread.
I guess it must be called before. (?)
And perhaps it can be called after to confirm/check the status. Maybe I can amend my While...Wend wait to check by another GetExitCodeThread call? (will check/test this later)

----------


## DEXWERX

@LaVolpe: alarm bells went off when you mentioned GDI+ and multithreading...

The GDI+ API is mostly thread safe, but the objects themselves need to be synchronized manually (which you already knew)

but even then I still wouldn't recommend it. 

http://stackoverflow.com/questions/3...e-resizing-net




> Sooner or later, with multi-threading in-process GDI operations, you'll run into deadlocks or completely random failures. After escalating one example to Microsoft PSS, they acknowledged corruption of the stack/heap due to some race conditions, but no real solution.

----------


## LaVolpe

@Dex. Thanx for the heads up. Another, likely more robust, solution would be to pass the image data vs the handle. This can be accomplished by retrieving the ARGB array from the handle, passing that array for complex processing, then returning the array to update that handle or create a new handle from that array. If creating the handle in a bkg thread, likewise, could pass the array to main thread for creation of image and bkg thread could destroy/release the created source handle. Not sure whether it's safe to use the same GDI+ instance created in the main thread or whether the bkg thread should start/stop GDI+ if it will be loading the image. Lots of tests needed and more research.

----------


## Krool

So I decided now to completly abandon the regular events. All will be in the IThread interface. Thus the Owner argument is mandatory in the Create function.
The interface looks now as following:


```
Public Sub BackgroundProcedure(ByVal Key As String, ByVal StatusCallback As IThreadStatusCallback)
End Sub

Public Sub StatusCallback(ByVal Key As String, ByRef Argument1 As Variant, ByRef Argument2 As Variant)
End Sub
```

BackgroundProcedure is the routine in the background thread.
StatusCallback is the routine in the main thread, mainly used to give a status update from the background thread, e.g. progress percentage etc.

I also included a 'SyncMode' property. (Default is False)
When set to True, this does allow to safely set breakpoints in the background routine, and perform debugging.
So typical usage could be then:


```
If InIDE() = True Then BackgroundWorker.SyncMode = True
```

Note that all of this information is still 'Work in Progress', only for information.

----------


## xxdoc123

> I also have the idea of bringing up a 'ThreadCancelBoolean' class which has a Value Boolean property (default property) that is passed along 'AsyncProcedure'.
> Kind of "AsyncProcedure(SyncCallback As IThreadSyncCallback, Cancel As ThreadCancelBoolean)
> In the Thread class there would be a Cancel method to make a 'soft terminate'.
> So in the AsyncProcedure there could be an "If Cancel = True" be included by the coder to bring the AsyncProcedure faster to it's end. 
> 
> Concerning the description. I do find Async.. and Sync.. already as self-explained? Or?


if i want 50 threads to download 50 files. how can i set files address to each thread? and monitor each file download progress?

----------


## Arnoutdv

You want 50 threads at the same time? Why not have 2-4 threads and download them in a queue?
What will you gain with 50 downloads at the same time?

----------


## xxdoc123

> You want 50 threads at the same time? Why not have 2-4 threads and download them in a queue?
> What will you gain with 50 downloads at the same time?


I'm just assuming,don't used 50 threads really..

----------


## xxdoc123

can you give a example

----------


## Eduardo-

> if i want 50 threads to download 50 files. how can i set files address to each thread? and monitor each file download progress?


You don't need to create threads for that, use the WinHTTPRequest object or Usercontrols.

But I think you should post further questions about this in a new forum thread.

----------


## Krool

I might rename 'SyncMode' (new, not yet implemented) to 'DebugMode'? Sounds more intiutive?

Also about TerminateThread. I can't really check if how I understood MSDN and used it is correct or not. I do not really find examples in google, they all just say do not use etc...

----------


## DEXWERX

You could even set DebugMode automatically ( GetModuleHandle("vba6"))

I don't think TerminateThread would be very nice to the COM STA, on your background thread :/
but we're in uncharted territory  :Smilie: 

and since you're adding everyone else's suggestions - maybe you can architect in a threadpool, 
where you have some control over the STA's message loop. then you can use messaging to initiate thread tasks, and reliably marshal interfaces both ways. 

this would also reduce the overhead in initializing COM on each thread.

I like this component, it's already very usable, even without any changes.

----------


## Krool

> You could even set DebugMode automatically ( GetModuleHandle("vba6"))
> 
> I don't think TerminateThread would be very nice to the COM STA, on your background thread :/
> but we're in uncharted territory 
> 
> and since you're adding everyone else's suggestions - maybe you can architect in a threadpool, 
> where you have some control over the STA's message loop. then you can use messaging to initiate thread tasks, and reliably marshal interfaces both ways. 
> 
> this would also reduce the overhead in initializing COM on each thread.
> ...


I don't want fix DebugMode when in IDE. Because in my testings it does "work" in the IDE, but there is a risk of a crash. The only thing what would make sense is to dissallow DebugMode when not in IDE. But anyhow I'm not a fan of such restriction.

About your message loop idea. That does not make sense actually. The thread sends a request to the client and there you can make whatever you like until that returns. So in the BackgroundProcedure (currently known as AsyncProcedure) you can do your own loop to whatever condition.

----------


## Krool

> Maybe a terminate event can be raised after the thread is killed. But additionally, passing a value to that event that is set inside the SyncCallback event. Maybe your ThreadCancelBoolean idea can have another property (variant, user defined)? And that value is passed to the terminate event? As a workaround, I was considering a timer event or PostMessage type event (asynchronous). However, a terminate event with optional value would be ideal I think.


I was able to fire a IThread_Complete when the thread is terminated, trough a Timer in the Ax-DLL that checks for:


```
WaitForSingleObject(PropThreadHandle, 0) = WAIT_OBJECT_0
```

However, there is a small detail to check.
When that IThread_Complete is fired, should the PropThreadHandle already be closed? (via CloseHandle)
If yes, then it would be possible to create the "same thread" again in the Complete "event". (like a recurrence)

EDIT: I will allow recurrence, as depending on the result it perhaps is necessary to "re-run" the background procedure.

----------


## Krool

Major updated released to version 1.1.

The version 1.1 needs a new manifest in order to run registration-free. The manifest can be found here.
The demo project is also a little bit enhanced to reflect some improvements and the increased flexibility.
All changes are described in the list of revisions. However, I will explain them again in more detail now.

The Thread class is not event driven anymore, instead it is interface driven.
This means an IThread interface will receive all the thread related "events".
Thus in the Create method there is an Owner parameter included, which is mandatory.
In this you specify the receiver object, normally a Form. But it can also be in another class. (as long as IThread is implemented)
The optional Key parameter in the Create method will help you for sure when you want or need to create more than one thread on the same receiver.

The AsyncProcedure event has been renamed to BackgroundProcedure and SyncCallback got renamed to StatusCallback.
The new included Complete event will fire after the BackgroundProcedure is returned and the thread handle is finally closed.

Included the new ThreadData class object that will be passed in BackgroundProcedure and Complete event.
That Data param has a Tag, Canceled and CancellationPending and DebugMode property. (DebugMode described later on)
The Tag property can be used to store some final data to be processed in the Complete event. The CancellationPending is read-only and returns True when from "outside" a cancel request was sent via the new included Cancel method. This is the preferred method to cancel a thread, instead of terminating it. However, how fast and efficient the cancel request is is totally dependent on the developer and the code in the BackgroundProcedure.
When CancellationPending is True and you want to exit the BackgroundProcedure then you may also set the Canceled property to True.
This way in the Complete event you can evaluate if the task was completed or canceled and if necessary re-start directly.

Included an optional Wait parameter in the Terminate method, so the method returns only until the thread is finally terminated. (not recommended, use new cancel request approach)

Also included the DebugMode property. If set to True the background thread runs synchronous to the main thread. This does allow to safely set breakpoints and perform debugging in the IDE.
The Data object (ThreadData) will also hold a copy of the DebugMode property, so in the BackgroundProcedure you can check for Data.DebugMode and do so some DoEvents in that case to keep the UI responsive while debugging in the IDE.

----------


## Krool

The only "issue" left is when setting the Thread class to Nothing (Set) while the thread is running the Class_Terminate will not be fired as a shadow reference is being held by the background thread.
So before doing Set = Nothing, do a .Terminate before.
However that should be only done in an emergency case.

----------


## LaVolpe

Krool, the description of the changes sounds really nice. Should give coders far more flexibility. 

If you consider this, I think the only major enhancement remaining would be to create this in a standard DLL vs. an ActiveX DLL. This is something I've been contemplating but not gotten serious about yet. The idea could allow ocx's, for example, to drop the standard DLL in its install folder and create threads without worrying about DLL registration. I'm still a bit green around the edges when it comes to multi-threading. I don't know if your SxS manifest would apply to a compiled ocx or not? Still much to learn myself.

----------


## Krool

> I don't know if your SxS manifest would apply to a compiled ocx or not? Still much to learn myself.


It should be possible to include the manifest for the dll in the ocx.
And the app who finally use the ocx can implement a manifest for the ocx.
Did not try.. maybe the final app needs to manifest both ocx and dll. (?) Or maybe only when the dll got exposed in the ocx, if used privately not needed. (?)
This would require testings to give a sure answer.

----------


## Krool

The manifest resource ID must be 2 instead of 1 when including the dll manifest in a ocx. However, testings needed.

----------


## LaVolpe

> maybe the final app needs to manifest both ocx and dll


From what I've read, any app that tries to use an ocx SxS without registration, needs to include the ocx and its dependencies (i.e., the dll). There may be examples on this site; I'll research when I find time.




> The manifest resource ID must be 2 instead of 1 when including the dll manifest in a ocx. However, testings needed.


Good point and worth highlighting

----------


## DEXWERX

anecdotal evidence but this new version (with DebugMode = False) 
seems more stable in the IDE than version 1.

App.ThreadID can be called without issue from the IThread_BackgroundProcedure()

*edit:* this seems ready for primetime. 
FYI  the .NET equivalent is the BackgroundWorker class, which might be closer to VBMThread10, due to using (less efficient) events. 

I really don't have anything to critique. (You could flesh out the entire .NET threading system, but this is awesome for 99% of tasks VB is used for)

How's your flexgrid coming?  :Smilie: 

 :Thumb:

----------


## Kunical

> Why would you need this in an ActiveX EXE, considering an ActiveX EXE can use VB6's standard multithreading methods?
> (setting the Threading Model)


Hi,

Sorry i meant to merge it with a standard exe to avoid the Dll dependencies.

----------


## quickbbbb

Hi ~  Krool

I find a program

please help me, thank you.


VB6 Code Sample [Attachment Removed by Moderator for containing an executable]

----------


## Krool

> I find a program
> 
> please help me, thank you.


Please explain in more detail and provide real source code, no finished .exe (!) file.

----------


## quickbbbb

===================
source code 
[Attachment Removed by Moderator for containing an executable]
===================

*Private Sub IThread_BackgroundProcedure*(ByVal Key As String, ByVal StatusCallback As VBMThread11.IThreadStatusCallback, ByVal Data As VBMThread11.ThreadData)

For w = 1 To 10^9 ' a large loop

    Text1 = Val(Text1) + 1  ' TextBox control add 1

If Check_Auto_Pause.Value Then
   If w Mod 1000 = 0 Then
      Call cmd_Start_Pause_Click 

*' about detail
    '(1) when w mod 1000 =0 , program will call  cmd_Start_Pause_Click , and  IThread will be pause
    '(2) Then I use mouse click botton cmd_Start_Pause, but IThread can not resume to run

*  
    End If
End If

Next
*End Sub*

*Private Sub cmd_Start_Pause_Click*() 

*' when click repeat , will  pause -> start -> pause -> start ........*

On Error Resume Next
Thread.Suspended = Not Thread.Suspended
Text3 = "Suspended_Ststus= " & Thread.Suspended
*End Sub*

----------


## quickbbbb

*detail  part 2*

check_Auto_Pause *is not selected *  and  use mouse click botton start/pause *->* 
textbox will  appear 1,2,3,4,5.....1000 *->* 
use mouse click botton start/pause again *->*  textbox add 1 will Suspended thread *->*  
use mouse click botton start/pause again *->*  textbox add 1 will resume thread run *->*  .....

*It work very good*


*detail  part 3*

check_Auto_Pause *is selected *  and  use mouse click botton start/pause *->* 
textbox will  appear 1,2,3,4,5.....1000 *->* 
when textbox.text = 1000 , thread will be suspended auto by program *->* 
now use mouse click botton start/pause again *->*  
*but thread can not resume to run*

----------


## Krool

It is not recommended to access project objects within BackgroundProcedure, unless there are created in it. Instead put them to StatusCallback. (especially the "Call cmd_Start_Pause_Click")

----------


## quickbbbb

ok , thank you.


I forget remove exe file

now there are only text code

*Source code* 

My_Sample.zip

----------


## quickbbbb

* ' crash when call class sub qq*

Private Sub IThread_BackgroundProcedure(ByVal Key As String, ByVal StatusCallback As VBMThread11.IThreadStatusCallback, ByVal Data As VBMThread11.ThreadData)

Text1 = Val(Text1) + 1

 c0.qq  'call class sub qq

End Sub

===============
*Source Code*
My_Sample_Crash.zip

----------


## quickbbbb

very sorry 

source code lost some file 

reupload

My_Sample_Class_Crash3.zip

----------


## Krool

Again: use StatusCallback when you want to access objects created NOT within the BackgroundProcedure.

----------


## quickbbbb

thank you , successful!!

----------


## xxdoc123

hi Krool:


```
 For i = 1 To 10
        Set mthread(i) = New Thread

        If InIDE() = True Then
            'MsgBox "In the IDE the threads are synchronous to the main thread." & vbLf & "DebugMode is set to True. (determined by the InIDE() function)", vbInformation + vbOKOnly
            mthread(i).DebugMode = True '

        End If
       mthread(i).Create Me, "TD" + CStr(i)
        Next
```



```
Private Sub IThread_BackgroundProcedure(ByVal Key As String, _
                                        ByVal StatusCallback As VBMThread11.IThreadStatusCallback, _
                                        ByVal Data As VBMThread11.ThreadData)

    Dim i As Integer, IsDebugMode As Boolean

    IsDebugMode = Data.DebugMode

    
    For i = 1 To 10
        StatusCallback.Raise (Mid(Key, 3) - 1) * 10 + i

        sleep 100
        
        If IsDebugMode = True Then DoEvents
    Next

 

End Sub
```

if i used 10 threads,download 100 (10×10) pics .when i make exe and run.
The program is stuck and can not be moved using the mouse untile the 10 threads complete.

if i change sleep 100  to doevents. can not useful.  can have good ideas?

----------


## Krool

@ xxdoc123, you need to place your download code into BackgroundProcedure and not in StatusCallback.
All code should be in BackgroundProcedure. The StatusCallback is only a bridge to interact with the VB main thread, e.g. updating a ProgressBar etc.

----------


## xxdoc123

> @ xxdoc123, you need to place your download code into BackgroundProcedure and not in StatusCallback.
> All code should be in BackgroundProcedure. The StatusCallback is only a bridge to interact with the VB main thread, e.g. updating a ProgressBar etc.


now i change the code what you say. but can used in ide,not run if make to exe. can you have a litter time to look my code?

thanks.i am newbie,have poor knowledge about multithread.

----------


## Krool

@ xxdoc123, the problem lies in the fact that you use 'App.Path' within the BackgroundProcedure. You should store the save the path string within Form_Load and store it in a string which you then use within your GetData function that is called within BackgroundProcedure.

----------


## xxdoc123

> @ xxdoc123, the problem lies in the fact that you use 'App.Path' within the BackgroundProcedure. You should store the save the path string within Form_Load and store it in a string which you then use within your GetData function that is called within BackgroundProcedure.


yes,i have change the app.path,but if i make to exe can not run.msgbox project error...if run in vb ide will be ok..my system is win7. 32.  thanks. can you fixed ？put a project demo to me ?

----------


## xxdoc123

i know，can not used cls。

----------


## Krool

I have got it to work.

What you Need to do is as following:

form-Level declarations:


```
Private AppPath As String, http As clshttp

Implements IThread '
```

During Form_Load:


```
Private Sub Form_Load()

AppPath = App.Path
Set http = New clshttp
```

New GetData function:


```
Public Function GetData(temp As Integer) As Boolean '
    ' Sleep 200
    TestStr = "Test" + CStr(temp)

    'Dim http As clshttp
    'Set http = New clshttp

    If http.Download("http://www.vbforums.com/images/misc/logo.gif", AppPath & "\test\" & TestStr & ".gif") = True Then
    
        GetData = True

    End If

    'Set http = Nothing

End Function
```

----------


## xxdoc123

yes.your are right.now work ok.thanks

----------


## Krool

@ xxdoc123, one more thing.
You use the .Cancel method somewhere, in order to be meaningful you need to add the following within in For..Loop in the BackgroundProcedure.


```
If Data.CancellationPending = True Then
    Data.Canceled = True
    Exit For
End If
```

----------


## xxdoc123

> @ xxdoc123, one more thing.
> You use the .Cancel method somewhere, in order to be meaningful you need to add the following within in For..Loop in the BackgroundProcedure.
> 
> 
> ```
> If Data.CancellationPending = True Then
>     Data.Canceled = True
>     Exit For
> End If
> ```


thanks ,the project is my test. I just want to test it stability.the program works fine,Thank you for your work

----------


## Krool

This is just a question put in the room. Out of curiosity!

The VBMThread11.DLL will just work fine in 32-bit VBA office. Whether .DebugMode is True or False.

However, by certain registry hacks (https://techtalk.gfi.com/32bit-objec...t-environment/) it is possible to reference a 32-bit COM dll in 64-bit environment. (e.g. 64-bit VBA office)
So in 64-bit VBA office the referencing works. But the threading does only work when using .DebugMode = True. (single-threaded)
When changing .DebugMode = False (true multi-threading) nothing happens. I mean really nothing. Not even a crash happens. The only thing happens is that the active UserForm becomes inactive, but the BackgroundProcedure in IThread never got called.

I don't know if anybody can give me an answer to this. Just wanted to put this out, as said, out of curiosity..

----------


## Krool

I guess I have the answer to my question.

It seems the registry tweak just allows to interoperability between 64bit and 32bit in a "interface wise" mean.
So 32bit can still not be "hosted" in 64bit environment. And by hosted is kind of an ActiveX control or in this sense a "call" from a 32bit thread to a 64bit environment.
The reason why it works with .DebugMode = True is because as the main 64bit thread will run its own call.

So the benefits of that DllSurrogate registry tweaks are limited..

----------


## wqweto

These registry "hacks" seem to register the x86 component as a COM+ application which as a consequence makes you library an out-of-process component for the x64 client.

cheers,
</wqw>

----------


## XXXMEN

Is any help from Microsoft Website?
https://msdn.microsoft.com/en-us/lib...(v=vs.85).aspx

----------


## Krool

> These registry "hacks" seem to register the x86 component as a COM+ application which as a consequence makes you library an out-of-process component for the x64 client.
> 
> cheers,
> </wqw>


"Out-of-process" is a perfect description for that case. Thanks

----------


## Krool

Included LinkSwitch /OPT:NOWIN98 on the VBCompiler which reduced the file size of the DLL from 48KB to 36KB

----------


## VBDevelopper

Hi for all
I need to use multithreading. I did a test using the VBMThread11 DLL.
I downloaded the xxdoc123 test program.
I do not understand why I do not receive an indication of the end of the thread execution with the msgbox inserted for this. :Confused: 
Furthermore, the sequence of thread execution does not seem to be as expected.
Thanks

----------


## Krool

> Hi for all
> I need to use multithreading. I did a test using the VBMThread11 DLL.
> I downloaded the xxdoc123 test program.
> I do not understand why I do not receive an indication of the end of the thread execution with the msgbox inserted for this.
> Furthermore, the sequence of thread execution does not seem to be as expected.
> Thanks


You have a logic error, try instead of:


```
Private Sub IThread_Complete(ByVal Key As String, ByVal Data As VBMThread11.ThreadData)

    If Data.Canceled = False Then
        Debug.Print Mid(Key, 3) & "complete"
        Debug.Print Mid(Key, 3) & "mThreadid=" & mThread((Mid(Key, 3))).hThread
    Else
        MsgBox Mid(Key, 3) & "mThread have end", vbInformation + vbOKOnly
    End If

End Sub
```

the following:


```
Private Sub IThread_Complete(ByVal Key As String, ByVal Data As VBMThread11.ThreadData)
If Data.Canceled = False Then
    If Data.DebugMode = True Then
        Debug.Print Mid(Key, 3) & "complete"
        Debug.Print Mid(Key, 3) & "mThreadid=" & mThread((Mid(Key, 3))).hThread
    Else
        MsgBox Mid(Key, 3) & "mThread have end", vbInformation + vbOKOnly
    End If
End If
End Sub
```

----------


## ChenLin

The test was successful, but unfortunately can not be used for database queries: in the query did not complete the progress bar or not move.

----------


## Krool

> The test was successful, but unfortunately can not be used for database queries: in the query did not complete the progress bar or not move.


If you use directly on SQL server it might work.
If you use on jet access db then not as async processing is not possible. The task are done in a queue one by one so even multithreading does not help.

Edit: And for example when using ADO you can then execute a query in StatusCallback via the main project ADODB conn with the dbAsync option. But again, does not work on access db.

----------


## ChenLin

> If you use directly on SQL server it might work.
> If you use on jet access db then not as async processing is not possible. The task are done in a queue one by one so even multithreading does not help.
> 
> Edit: And for example when using ADO you can then execute a query in StatusCallback via the main project ADODB conn with the dbAsync option. But again, does not work on access db.




```
Public Function ExecuteSQL(ByVal SqlStr As String, Optional ErrMsg As String) As ADODB.Recordset
0   On Error GoTo ExectueSql_Error
    Dim Mycon As ADODB.Connection
    Dim rst As ADODB.Recordset
    Set Mycon = New ADODB.Connection
    Mycon.ConnectionString = ConnString
    Mycon.ConnectionTimeout = Val(lTmr) + 1
    Mycon.Open
    
    Dim stokens() As String
    On Error GoTo ExectueSql_Error
    stokens = Split(SqlStr)
    If InStr("INSERT,DELETE,UPDATE", UCase(stokens(0))) Then
        Mycon.Execute SqlStr
    Else
        Set rst = New ADODB.Recordset
        rst.CursorLocation = adUseClient
        'rst.Properties("Initial Fetch Size") = 50
        rst.Open Trim(SqlStr), Mycon, adOpenKeyset, adLockOptimistic ',adCmdText
        Set ExecuteSQL = rst
    End If
ExectueSql_Exit:
    Set rst = Nothing
    Set Mycon = Nothing
    Exit Function
ExectueSql_Error:
    If err.Number = -2147467259 Then '解决 Named Pipes 错误
        SaveRegistryKey HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo", strServerName, "DBMSSOCN," & strServerName
        ErrMsg = "连接数据库服务器 " & " 错误：" & vbCrLf & vbCrLf & err.Description
    Else
        ErrMsg = err.Description
    End If
    'WriteErrLog ErrMsg
    Resume ExectueSql_Exit
End Function
```

I'm using this. May I have this, please?
Yesterday's test was ADO data control, which has been tested.

----------


## VBDevelopper

> You have a logic error, try instead of:
> 
> 
> ```
> Private Sub IThread_Complete(ByVal Key As String, ByVal Data As VBMThread11.ThreadData)
> 
>     If Data.Canceled = False Then
>         Debug.Print Mid(Key, 3) & "complete"
>         Debug.Print Mid(Key, 3) & "mThreadid=" & mThread((Mid(Key, 3))).hThread
> ...


With your suggestion the program works.
you excuse my banality, but I did not understand some things.
1) 
Because we need the timer to check if all the threads are finished.
The IThread_Complete event could not be used for this.
2) 
The IThread_BackgroundProcedure is a procedure is not an event? Quite right?
3)
Is IThread_Complete an event?
It is not generated when the thread ends as the name would seem to say.
4) The IThread_StatusCallback event is generated by the program. But is it always like this? 
The thread that is executed does not generate it?
Thank

----------


## Krool

> With your suggestion the program works.
> you excuse my banality, but I did not understand some things.
> 1) 
> Because we need the timer to check if all the threads are finished.
> The IThread_Complete event could not be used for this.
> 2) 
> The IThread_BackgroundProcedure is a procedure is not an event? Quite right?
> 3)
> Is IThread_Complete an event?
> ...


IThread is an interface definition and *not* event driven. It's called by interface methods.

Interface method when a thread's background procedure is called.


```
Public Sub BackgroundProcedure(ByVal Key As String, ByVal StatusCallback As IThreadStatusCallback, ByVal Data As ThreadData)

StatusCallback.Raise 1&, "Test" ' StatusCallback method called.

End Sub
```

Interface method when a thread's background procedure has been completed or canceled.
The Thread is already terminated (hThread = 0) when this method is called.


```
Public Sub Complete(ByVal Key As String, ByVal Data As ThreadData)

End Sub
```

Interface method when a thread requests an synchronous status callback. The background thread will be suspended, the method request is marshaled to the main thread.


```
Public Sub StatusCallback(ByVal Key As String, ByRef Argument1 As Variant, ByRef Argument2 As Variant)

End Sub
```

----------


## VBDevelopper

Hi Krool.
Thanks.
Now it is a bit more clear.
You have to excuse me, but I now approach this advanced programming.
I still have a small question.
Why is this thread called "(Multithreading generic)"?
Are there any other types of multithread?

----------


## Krool

> Hi Krool.
> Thanks.
> Now it is a bit more clear.
> You have to excuse me, but I now approach this advanced programming.
> I still have a small question.
> Why is this thread called "(Multithreading generic)"?
> Are there any other types of multithread?


The official VB6 multithreading is a ActiveX EXE. Like a COM DLL it must be registered.
However, "your code" must be placed inside that ActiveX EXE. So for each purpose or task you may need to change that ActiveX EXE or create another one.

This generic DLL allows to place "your code" inside your "own app". So for whatever task or purpose you can change "your app".
This is meant by "generic". The DLL remains always unchanged.

----------


## VBDevelopper

but if there are the activeX :Confused:  because one needs to use other paths?

----------


## DEXWERX

> but if there are the activeX  because one needs to use other paths?


FYI ActiveX EXE's can't be run without either being "installed" with administrator rights, or run the first time with administrator rights, where it registers it's interfaces.
Krool's DLL however can be used SxS, without the need for admin rights.

----------


## VBDevelopper

OK. Thanks

----------


## Krool

> ```
> Public Function ExecuteSQL(ByVal SqlStr As String, Optional ErrMsg As String) As ADODB.Recordset
> 0   On Error GoTo ExectueSql_Error
>     Dim Mycon As ADODB.Connection
>     Dim rst As ADODB.Recordset
>     Set Mycon = New ADODB.Connection
>     Mycon.ConnectionString = ConnString
>     Mycon.ConnectionTimeout = Val(lTmr) + 1
>     Mycon.Open
> ...


May have what?

The best is you create the ADODB.Connection and ADODB.Recordset within the IThread_BackgroundProcedure from scratch (Early-bound works from main thread references) and then just execute or do your queries form there and communicate your results via StatusCallback. No dbAsyncExecute needed.
However, as said when using access mdb files it's not working since file locking. On SQL server it should work just well.

----------


## wqweto

JFYI, when sample project VBMThread11Demo.zip is compiled to P-Code all samples seem to freeze which is very weird.

The multi-threader is left natively compiled DLL, only the IThread interface implementation is P-Code compiled and still the runtime doesn't like this scenario.

cheers,
</wqw>

----------


## Krool

> JFYI, when sample project VBMThread11Demo.zip is compiled to P-Code all samples seem to freeze which is very weird.
> 
> The multi-threader is left natively compiled DLL, only the IThread interface implementation is P-Code compiled and still the runtime doesn't like this scenario.


Added a note in thread's first post that P-Code will fail. So maybe it's more noticed for new users.

----------

