CreateThread() vs. CreateVOThread()
Posted: Thu Aug 05, 2021 10:12 am
(This is basically a copy+paste of my question over on comp.lang.clipper.visual-objects. The guys over there suggested I bring it here to try and get Robert's attention.)
Hi All,
I'm wondering if anyone knows what the differences between Win32 API CreateThread() vs. VO's CreateVOThread() are? Mostly with relation to the garbage collector. I.e. if I use Win32 API's CreateThread() would I completely avoid the garbage collector running on my thread. Is there a reason why I would still be better off using CreateVOThread()?
I understand that threads and dynamic memory / the garbage collector don't play well together. I have in my ThreadFunc avoided declaring any LOCAL's and avoided calling any VO functions (because they might also have LOCAL's). There are also no anonymous variables like a string concatenation. Where I can't avoid a variable I use a structure (ThreadData) in static memory. The thread is the only reader/writer to this structure.
Assuming I haven't missed anything, my thread is only working in static memory and only calling Windows API functions.
The thread itself is pretty simple. It calls Win32 API IsHungAppWindow() once per second and logs an event in the windows event log if that function returns true.
I'm not sure how much I trust VO's threading (or my code not to have missed some dynamic memory allocation somewhere) so I've also included runtime options to completely disable this feature if it plays up.
Cheers!
Rob.
I guess I will post the code of my thread func, given the responses I'm getting
And the question I'm trying to have answered: Would the garbage collector ever initiate on this thread if I start it with CreateThread()? I don't want it to.
STRUCT HeartBeatMonitor_ThreadData
MEMBER hWnd AS PTR
MEMBER hEventSource AS PTR
MEMBER pszEventSource AS PSZ
MEMBER RootDirectory AS PSZ
MEMBER sTimeStart AS _winSYSTEMTIME
MEMBER sTimeEnd AS _winSYSTEMTIME
FUNCTION HeartbeatMonitor(sHeartBeatMonitor_ThreadData AS HeartBeatMonitor_ThreadData) AS DWORD PASCAL
// Do not declare any locals here. Garbage collector is not thread safe.
// Also must not call any functions including VO builtins that might allocate memory.
DO WHILE TRUE
IF sHeartBeatMonitor_ThreadData.hWnd != NULL_PTR
IF IsHungAppWindow(sHeartBeatMonitor_ThreadData.hWnd)
IF sHeartBeatMonitor_ThreadData.sTimeStart == NULL_PTR
sHeartBeatMonitor_ThreadData.sTimeStart := MemAlloc(_SizeOf(_winSYSTEMTIME))
GetLocalTime(sHeartBeatMonitor_ThreadData.sTimeStart)
sHeartBeatMonitor_ThreadData.hEventSource := RegisterEventSource(NULL_PSZ, sHeartBeatMonitor_ThreadData.pszEventSource)
ReportEvent(sHeartBeatMonitor_ThreadData.hEventSource, EVENTLOG_WARNING_TYPE, 0, MSG_APPLICATION_HANG, NULL_PTR, 1, 0, @sHeartBeatMonitor_ThreadData.RootDirectory, NULL_PTR)
DeregisterEventSource(sHeartBeatMonitor_ThreadData.hEventSource)
sHeartBeatMonitor_ThreadData.hEventSource := NULL_PTR
END IF
ELSE
IF sHeartBeatMonitor_ThreadData.sTimeStart != NULL_PTR
sHeartBeatMonitor_ThreadData.sTimeEnd := MemAlloc(_SizeOf(_winSYSTEMTIME))
GetLocalTime(sHeartBeatMonitor_ThreadData.sTimeEnd)
sHeartBeatMonitor_ThreadData.hEventSource := RegisterEventSource(NULL_PSZ, sHeartBeatMonitor_ThreadData.pszEventSource)
ReportEvent(sHeartBeatMonitor_ThreadData.hEventSource, EVENTLOG_INFORMATION_TYPE, 0, MSG_APPLICATION_RESUME, NULL_PTR, 1, 0, @sHeartBeatMonitor_ThreadData.RootDirectory, NULL_PTR)
DeregisterEventSource(sHeartBeatMonitor_ThreadData.hEventSource)
sHeartBeatMonitor_ThreadData.hEventSource := NULL_PTR
MemFree(sHeartBeatMonitor_ThreadData.sTimeStart)
MemFree(sHeartBeatMonitor_ThreadData.sTimeEnd)
sHeartBeatMonitor_ThreadData.sTimeStart := NULL_PTR
sHeartBeatMonitor_ThreadData.sTimeEnd := NULL_PTR
END IF
END IF
END IF
Sleep(1000)
END DO
RETURN 0
This function runs on the main thread to launch the thread:
FUNCTION HeartbeatMonitorInit(hWnd AS PTR) AS VOID PASCAL
LOCAL sSECURITY_ATTRIBUTES AS _winSECURITY_ATTRIBUTES
LOCAL sHeartBeatMonitor_ThreadData AS HeartBeatMonitor_ThreadData
sSECURITY_ATTRIBUTES := MemAlloc(_SizeOf(_winSECURITY_ATTRIBUTES))
sSECURITY_ATTRIBUTES.nLength := _SizeOf(_winSECURITY_ATTRIBUTES)
sSECURITY_ATTRIBUTES.lpSecurityDescriptor := NULL_PTR
sSECURITY_ATTRIBUTES.bInheritHandle := FALSE
sHeartBeatMonitor_ThreadData := MemAlloc(_SizeOf(HeartBeatMonitor_ThreadData))
sHeartBeatMonitor_ThreadData.hWnd := hWnd
sHeartBeatMonitor_ThreadData.pszEventSource := PszAlloc(String2PSZ("XXXXX"))
sHeartBeatMonitor_ThreadData.RootDirectory := PszAlloc(String2PSZ(gcRootDirectory))
sHeartBeatMonitor_ThreadData.sTimeStart := NULL_PTR
sHeartBeatMonitor_ThreadData.sTimeEnd := NULL_PTR
CreateThread(sSECURITY_ATTRIBUTES, 0, @HeartbeatMonitor(), sHeartBeatMonitor_ThreadData, 0, NULL_PTR)
MemFree(sSECURITY_ATTRIBUTES)
RETURN
Cheers!
Hi All,
I'm wondering if anyone knows what the differences between Win32 API CreateThread() vs. VO's CreateVOThread() are? Mostly with relation to the garbage collector. I.e. if I use Win32 API's CreateThread() would I completely avoid the garbage collector running on my thread. Is there a reason why I would still be better off using CreateVOThread()?
I understand that threads and dynamic memory / the garbage collector don't play well together. I have in my ThreadFunc avoided declaring any LOCAL's and avoided calling any VO functions (because they might also have LOCAL's). There are also no anonymous variables like a string concatenation. Where I can't avoid a variable I use a structure (ThreadData) in static memory. The thread is the only reader/writer to this structure.
Assuming I haven't missed anything, my thread is only working in static memory and only calling Windows API functions.
The thread itself is pretty simple. It calls Win32 API IsHungAppWindow() once per second and logs an event in the windows event log if that function returns true.
I'm not sure how much I trust VO's threading (or my code not to have missed some dynamic memory allocation somewhere) so I've also included runtime options to completely disable this feature if it plays up.
Cheers!
Rob.
I guess I will post the code of my thread func, given the responses I'm getting
And the question I'm trying to have answered: Would the garbage collector ever initiate on this thread if I start it with CreateThread()? I don't want it to.
STRUCT HeartBeatMonitor_ThreadData
MEMBER hWnd AS PTR
MEMBER hEventSource AS PTR
MEMBER pszEventSource AS PSZ
MEMBER RootDirectory AS PSZ
MEMBER sTimeStart AS _winSYSTEMTIME
MEMBER sTimeEnd AS _winSYSTEMTIME
FUNCTION HeartbeatMonitor(sHeartBeatMonitor_ThreadData AS HeartBeatMonitor_ThreadData) AS DWORD PASCAL
// Do not declare any locals here. Garbage collector is not thread safe.
// Also must not call any functions including VO builtins that might allocate memory.
DO WHILE TRUE
IF sHeartBeatMonitor_ThreadData.hWnd != NULL_PTR
IF IsHungAppWindow(sHeartBeatMonitor_ThreadData.hWnd)
IF sHeartBeatMonitor_ThreadData.sTimeStart == NULL_PTR
sHeartBeatMonitor_ThreadData.sTimeStart := MemAlloc(_SizeOf(_winSYSTEMTIME))
GetLocalTime(sHeartBeatMonitor_ThreadData.sTimeStart)
sHeartBeatMonitor_ThreadData.hEventSource := RegisterEventSource(NULL_PSZ, sHeartBeatMonitor_ThreadData.pszEventSource)
ReportEvent(sHeartBeatMonitor_ThreadData.hEventSource, EVENTLOG_WARNING_TYPE, 0, MSG_APPLICATION_HANG, NULL_PTR, 1, 0, @sHeartBeatMonitor_ThreadData.RootDirectory, NULL_PTR)
DeregisterEventSource(sHeartBeatMonitor_ThreadData.hEventSource)
sHeartBeatMonitor_ThreadData.hEventSource := NULL_PTR
END IF
ELSE
IF sHeartBeatMonitor_ThreadData.sTimeStart != NULL_PTR
sHeartBeatMonitor_ThreadData.sTimeEnd := MemAlloc(_SizeOf(_winSYSTEMTIME))
GetLocalTime(sHeartBeatMonitor_ThreadData.sTimeEnd)
sHeartBeatMonitor_ThreadData.hEventSource := RegisterEventSource(NULL_PSZ, sHeartBeatMonitor_ThreadData.pszEventSource)
ReportEvent(sHeartBeatMonitor_ThreadData.hEventSource, EVENTLOG_INFORMATION_TYPE, 0, MSG_APPLICATION_RESUME, NULL_PTR, 1, 0, @sHeartBeatMonitor_ThreadData.RootDirectory, NULL_PTR)
DeregisterEventSource(sHeartBeatMonitor_ThreadData.hEventSource)
sHeartBeatMonitor_ThreadData.hEventSource := NULL_PTR
MemFree(sHeartBeatMonitor_ThreadData.sTimeStart)
MemFree(sHeartBeatMonitor_ThreadData.sTimeEnd)
sHeartBeatMonitor_ThreadData.sTimeStart := NULL_PTR
sHeartBeatMonitor_ThreadData.sTimeEnd := NULL_PTR
END IF
END IF
END IF
Sleep(1000)
END DO
RETURN 0
This function runs on the main thread to launch the thread:
FUNCTION HeartbeatMonitorInit(hWnd AS PTR) AS VOID PASCAL
LOCAL sSECURITY_ATTRIBUTES AS _winSECURITY_ATTRIBUTES
LOCAL sHeartBeatMonitor_ThreadData AS HeartBeatMonitor_ThreadData
sSECURITY_ATTRIBUTES := MemAlloc(_SizeOf(_winSECURITY_ATTRIBUTES))
sSECURITY_ATTRIBUTES.nLength := _SizeOf(_winSECURITY_ATTRIBUTES)
sSECURITY_ATTRIBUTES.lpSecurityDescriptor := NULL_PTR
sSECURITY_ATTRIBUTES.bInheritHandle := FALSE
sHeartBeatMonitor_ThreadData := MemAlloc(_SizeOf(HeartBeatMonitor_ThreadData))
sHeartBeatMonitor_ThreadData.hWnd := hWnd
sHeartBeatMonitor_ThreadData.pszEventSource := PszAlloc(String2PSZ("XXXXX"))
sHeartBeatMonitor_ThreadData.RootDirectory := PszAlloc(String2PSZ(gcRootDirectory))
sHeartBeatMonitor_ThreadData.sTimeStart := NULL_PTR
sHeartBeatMonitor_ThreadData.sTimeEnd := NULL_PTR
CreateThread(sSECURITY_ATTRIBUTES, 0, @HeartbeatMonitor(), sHeartBeatMonitor_ThreadData, 0, NULL_PTR)
MemFree(sSECURITY_ATTRIBUTES)
RETURN
Cheers!