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.
Insomnia is just a byproduct of, "It can't be done"
If passing a string is not much overhead, I suggest to identify each threads with a name.
Code:
Implements IThread
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.
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.
What is 'CreateThread'. A public module function?
I think the "Thread.Create Me, 'Key123' " is the way I prefer.
OK.
Originally Posted by Krool
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.
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.
I got the IThread to work now in my new 1.1 version. (work in progress)
Code:
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:
Code:
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)
Code:
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.
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.
Originally Posted by msdn
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.
Code:
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.
Last edited by LaVolpe; Apr 23rd, 2017 at 07:58 PM.
Insomnia is just a byproduct of, "It can't be done"
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?
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.
Last edited by LaVolpe; Apr 23rd, 2017 at 07:50 PM.
Insomnia is just a byproduct of, "It can't be done"
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?
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.
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)
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.
@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.
Insomnia is just a byproduct of, "It can't be done"
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:
Code:
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:
Code:
If InIDE() = True Then BackgroundWorker.SyncMode = True
Note that all of this information is still 'Work in Progress', only for information.
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?
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...
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 like this component, it's already very usable, even without any changes.
Last edited by DEXWERX; Apr 25th, 2017 at 03:05 PM.
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 like this component, it's already very usable, even without any changes.
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.
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:
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.
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.
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.
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.
Insomnia is just a byproduct of, "It can't be done"
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.
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.
Originally Posted by Krool
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
Insomnia is just a byproduct of, "It can't be done"
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?
Last edited by DEXWERX; May 5th, 2017 at 12:42 PM.
===================
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
Last edited by RobDog888; Jun 3rd, 2017 at 12:25 PM.
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
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")
Private Sub IThread_BackgroundProcedure(ByVal Key As String, ByVal StatusCallback As VBMThread11.IThreadStatusCallback, ByVal Data As VBMThread11.ThreadData)