Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate

[AHK.dll] Multi-Threading Basic Examples


  • Please log in to reply
71 replies to this topic
  • Guests
  • Last active:
  • Joined: --
Hi,

I'm learning real multi-threading using AutoHotkey.dll. To get started, I'd like to know if this is possible.

This lists current existing processes and the associated command lines.
Gui, Process:Add, ListView, x2 y0 w400 h500, PID|Process Name|Command Line
Gui, Process: default
for process in ComObjGet("winmgmts:").ExecQuery("Select * from Win32_Process")
    LV_Add("", process.ProcessID, process.Name, process.CommandLine)
Gui, Process:Show,, Process List
This lists current existing windows and processes.
WinGet, WindowList, List
Gui, Windows:Add, ListView, x2 y0 w600 h500, PID|Process Name|Window Handle|Window Title
Gui, Windows: default
loop % WindowList
{
	WinGetTitle, WindowTitle , % "ahk_id " WindowList%A_Index%
	WinGet, ProcessName , ProcessName, % "ahk_id " WindowList%A_Index%
	WinGet, PID , PID, % "ahk_id " WindowList%A_Index%
    LV_Add("", PID, ProcessName, WindowList%A_Index%, WindowTitle)
}
LV_ModifyCol()
Gui, Windows:Show,, Window List
Each one of them can retrieve different information associated with process ID so I'd like to run them simultaneously and combine them into one object.

Thread A:
;oProc := {}	;assuming oProc is declared as an object already.
for process in ComObjGet("winmgmts:").ExecQuery("Select * from Win32_Process") {
	if !IsObject(oProc[PID])
		oProc[PID] := {}	
    oProc[PID] := {CommandLine : process.CommandLine
				, Name : process.Name}
}
threadA := true

Thread B:
;oProc := {}	;assuming oProc is declared as an object already.
WinGet, WindowList, List
loop % WindowList
{
	WinGetTitle, WindowTitle , % "ahk_id " WindowList%A_Index%
	WinGet, PID , PID, % "ahk_id " WindowList%A_Index%
	if !IsObject(oProc[PID])
		oProc[PID] := {}
	oProc[PID]["WindowTitle"] := WindowTitle
	oProc[PID]["WindowHandle"] := WindowList%A_Index%
}
threadB := true

Main Thread:
While !threadA || !threadB
	sleep 10
Gui, ProcAndWins: Add, ListView, x2 y0 w600 h500, PID|Process Name|Command Line|Window Handle|Window Title
Gui, ProcAndWins: default
For pid, oPid in oProc
	LV_Add("", pid, oPid.Name, oPid.CommandLine, oPid.WindowHandle, oPid.WindowTitle)
LV_ModifyCol()
Gui, ProcAndWins:Show,, Process and Window List
Could somebody provide a working example for this? Thanks for your help.

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
That's how I would do it currently ;)
#include <CreateScript>

#include <AhkDllThread>

oProc:=CriticalObject()

Loop 2

	Thread%A_Index%:=AhkDllThread(A_ScriptDir "\AutoHotkey.dll")

oProcScript:="oProc:=CriticalObject(" CriticalObject(oProc,1) "," CriticalObject(oProc,2) ")`n"

Thread1.ahktextdll(oProcScript CreateScript("<ThreadA:ThreadA>"))

Thread2.ahktextdll(oProcScript CreateScript("<Threadb:Threadb>"))



Gui, ProcAndWins: Add, ListView, x2 y0 w600 h500, PID|Process Name|Command Line|Window Handle|Window Title

Gui, ProcAndWins: default

While thread1.ahkReady() || thread2.ahkReady()

   sleep 10

For pid, oPid in oProc

   LV_Add("", pid, oPid.Name, oPid.CommandLine, oPid.WindowHandle, oPid.WindowTitle)

LV_ModifyCol()

Gui, ProcAndWins:Show,, Process and Window List

return

Escape::

GuiClose:

ExitApp



<ThreadA:

for process in ComObjGet("winmgmts:").ExecQuery("Select * from Win32_Process")

    PID:=process.processid,oProc[PID,"CommandLine"] := process.CommandLine,oProc[PID,"Name"] := process.Name

ThreadA>:

Return



<ThreadB:

WinGet, WindowList, List

loop % WindowList

{

   WinGetTitle, WindowTitle , % "ahk_id " WindowList%A_Index%

   WinGet, PID , PID, % "ahk_id " WindowList%A_Index%

   oProc[PID]["WindowTitle"] := WindowTitle

   oProc[PID]["WindowHandle"] := WindowList%A_Index%

}

ThreadB>:

Return


  • Guests
  • Last active:
  • Joined: --
Thanks HotKeyIt. I got questions.

CriticalObject() requires AutoHotkey_H. Is there a way to do this with AutoHotkey_L?

I dropped the script icon onto AutoHotkey.exe of AutoHotkey_H and the list view appeared. However, there are no values displayed in the Window Handle and Window Title colums. Do they appear in your environment?

I'm having hard time understanding the code as follows.
oProc:=CriticalObject()
So is CriticalObject to share oProc in different threads? Can I think this is like super global scope for real threads as thread to be funciton?

Loop 2
   Thread%A_Index%:=AhkDllThread(A_ScriptDir "\AutoHotkey.dll")
It looks like creating a thread object for each thread. So in order to run a thread, do we need to create a thread object?

oProcScript:="oProc:=CriticalObject(" CriticalObject(oProc,1) "," CriticalObject(oProc,2) ")`n"
OK, this is hard stuff. It seems it is required to declare an object with CriticalObject() in a local thread in order to access an object of the main thread. The first parameter is the address of the referencing object and the second parameter is said to be a pointer of critical section. But when have critical sections been created? Can I think a critical section as a thread and a pointer of critical section as a handle of a thread?

Thread1.ahktextdll(oProcScript CreateScript("<ThreadA:ThreadA>"))
Thread2.ahktextdll(oProcScript CreateScript("<Threadb:Threadb>"))
It seems a complete script is passed to the ahktextdll() method. After this line is called, will the passed script start as a real thread?

Thanks for your time.

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008

Thanks HotKeyIt. I got questions.

CriticalObject() requires AutoHotkey_H. Is there a way to do this with AutoHotkey_L?

Yes you have to use CriticalSection
Whenever you access the object then, you will have to Lock(YourSection) and UnLock(YourSection) when finished.

I dropped the script icon onto AutoHotkey.exe of AutoHotkey_H and the list view appeared. However, there are no values displayed in the Window Handle and Window Title colums. Do they appear in your environment?

I haven't noticed :oops:
ThreadB should be using , instead of ][ so new object is created when necessary.
[color=red]DetectHiddenWindows,On[/color]
WinGet, WindowList, List
loop % WindowList
{
   WinGetTitle, WindowTitle , % "ahk_id " WindowList%A_Index%
   WinGet, PID , PID, % "ahk_id " WindowList%A_Index%
   oProc[PID[color=red],[/color]"WindowTitle"] := WindowTitle
   oProc[PID[color=red],[/color]"WindowHandle"] := WindowList%A_Index%
}

I'm having hard time understanding the code as follows.

oProc:=CriticalObject()
So is CriticalObject to share oProc in different threads? Can I think this is like super global scope for real threads as thread to be funciton?

CriticalObject has nothing to do with global, since we share it to the thread manually.
Using CriticalObject(CriObj,[1=object ptr] or [2 = criticalsection ptr]) we can simply reproduce the CriticalObject it in another thread.
Also CriticalObject() does the Lock + Unlock automatically when object is accessed.

Loop 2
   Thread%A_Index%:=AhkDllThread(A_ScriptDir "\AutoHotkey.dll")
It looks like creating a thread object for each thread. So in order to run a thread, do we need to create a thread object?

Yes that is how AhkDllThread works.
Generally you do not need a thread object but a loaded AutoHotkey.dll.
Then you would have to use DllCall for all actions, e.g. DllCall("AutoHotkey.dll\ahktextdll",...)

oProcScript:="oProc:=CriticalObject(" CriticalObject(oProc,1) "," CriticalObject(oProc,2) ")`n"
OK, this is hard stuff. It seems it is required to declare an object with CriticalObject() in a local thread in order to access an object of the main thread. The first parameter is the address of the referencing object and the second parameter is said to be a pointer of critical section. But when have critical sections been created? Can I think a critical section as a thread and a pointer of critical section as a handle of a thread?

CriticalSection is created and initialized in CriticalObject() internally for simplicity, it is also not deleted or freed before main script exits. You could also use your own CriticalSection:
VarSetCapacity(CriticalSection,24)
DllCall("InitializeCriticalSection","Ptr",&CriticalSection)
obj:={}
oProc:=CriticalObject(obj,&CriticalSection)

Thread1.ahktextdll(oProcScript CreateScript("<ThreadA:ThreadA>"))
Thread2.ahktextdll(oProcScript CreateScript("<Threadb:Threadb>"))
It seems a complete script is passed to the ahktextdll() method. After this line is called, will the passed script start as a real thread?

That is correct.

Thanks for your time.

You are welcome ;)

  • Guests
  • Last active:
  • Joined: --
Thanks for the reply.

I tried to compare the speed of parallel threading to the single one.

This is for single threading.
StartTime := A_TickCount
loop 1000
{
	oProc := {}
	WinGet, WindowList, List
	loop % WindowList
	{
	   WinGetTitle, WindowTitle , % "ahk_id " WindowList%A_Index%
	   WinGet, PID , PID, % "ahk_id " WindowList%A_Index%
	   oProc[PID, "WindowTitle"] := WindowTitle
	   oProc[PID, "WindowHandle"] := WindowList%A_Index%
	}
	for process in ComObjGet("winmgmts:").ExecQuery("Select * from Win32_Process")
	{
		PID := process.processid
		oProc[PID,"CommandLine"] := process.CommandLine
		oProc[PID,"Name"] := process.Name
	}

}
msgbox % "Average Elapsed Milliseconds Per One Iteration:" A_Tab (A_TickCount - StartTime) // 1000

And if I try this to test the speed of multi-threading, the script crashes.
#include <CreateScript>
#include <AhkDllThread>
StartTime := A_TickCount
loop 1000
{
	oProc:=CriticalObject()
	Loop 2
	   Thread%A_Index%:=AhkDllThread(A_ScriptDir "\AutoHotkey.dll")
	oProcScript:="oProc:=CriticalObject(" CriticalObject(oProc,1) "," CriticalObject(oProc,2) ")`n"
	Thread1.ahktextdll(oProcScript CreateScript("<ThreadA:ThreadA>"))
	Thread2.ahktextdll(oProcScript CreateScript("<Threadb:Threadb>"))

	Gui, ProcAndWins: Add, ListView, x2 y0 w600 h500, PID|Process Name|Command Line|Window Handle|Window Title
	Gui, ProcAndWins: default
	While thread1.ahkReady() || thread2.ahkReady()
	   sleep 10
}
msgbox % "Average Elapsed Seconds Per One Iteration:" A_Tab (A_TickCount - StartTime) // 1000
return
Escape::
GuiClose:
ExitApp

<ThreadA:
for process in ComObjGet("winmgmts:").ExecQuery("Select * from Win32_Process")
{
    PID := process.processid
	oProc[PID,"CommandLine"] := process.CommandLine
	oProc[PID,"Name"] := process.Name
}
ThreadA>:
Return

<ThreadB:
WinGet, WindowList, List
loop % WindowList
{
   WinGetTitle, WindowTitle , % "ahk_id " WindowList%A_Index%
   WinGet, PID , PID, % "ahk_id " WindowList%A_Index%
   oProc[PID, "WindowTitle"] := WindowTitle
   oProc[PID, "WindowHandle"] := WindowList%A_Index%
}
ThreadB>:
Return

Also I noticed that if I put #NoTrayIcon in each thread, it is applied to the main script.

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
This is not very useful example because ThreadA takes around 97% of the time of the total operation, so it can't be much faster than in main thread.
Try running the 2 Loops in main thread separately to see what I mean.

Also using CriticalObject only makes sure that 2 threads do not access an object at the same time, it does not make the total operation faster but even a little slower.

So for example following script will be around 20% slower:
#include <AhkDllThread>
SetBatchLines,-1
oProc:=CriticalObject()
Loop 2
  Thread%A_Index%:=AhkDllThread(A_ScriptDir "\AutoHotkey.dll")
oProcScript:="oProc:=CriticalObject(" CriticalObject(oProc,1) "," CriticalObject(oProc,2) ")`nSetBatchLines,-1`n"

StartTime := A_TickCount ; here scripts will start up
Thread1.ahktextdll(oProcScript "Loop 100000`noProc[""1:"" A_Index]:=A_TickCount")
Thread2.ahktextdll(oProcScript "Loop 100000`noProc[""2:"" A_Index]:=A_TickCount")
While thread1.ahkReady() || thread2.ahkReady()
      sleep 100

msgbox % "Elapsed Seconds using Threads:" A_Tab (A_TickCount-StartTime)//1000
Compared to:
SetBatchLines,-1
oProc:={}
StartTime:=A_TickCount
Loop 100000
   oProc["1:" A_Index]:=A_TickCount,oProc["2:" A_Index]:=A_TickCount
msgbox % "Elapsed Seconds using Main:" A_Tab (A_TickCount-StartTime)//1000
ExitApp

So in the end to speed up things you will have to use 2 separate objects and definitely a different example ;)
For Example:
#include <AhkDllThread>
SetBatchLines,-1
oProc1:=CriticalObject()
oProc2:=CriticalObject()
Loop 2
  Thread%A_Index%:=AhkDllThread(A_ScriptDir "\AutoHotkey.dll")

StartTime := A_TickCount
Thread1.ahktextdll("oProc:=CriticalObject(" CriticalObject(oProc1,1) "," CriticalObject(oProc1,2) ")`nSetBatchLines,-1`nLoop 1000000`noProc1[""1:"" A_Index]:=A_Index**8")
Thread2.ahktextdll("oProc:=CriticalObject(" CriticalObject(oProc2,1) "," CriticalObject(oProc2,2) ")`nSetBatchLines,-1`nLoop 1000000`noProc2[""2:"" A_Index]:=A_Index**8")
While thread1.ahkReady() || thread2.ahkReady()
      sleep 10

msgbox % "Elapsed Seconds using Threads:" A_Tab (A_TickCount-StartTime)
Same in main thread, (around 30% slower)
SetBatchLines,-1
oProc1:=[],oProc2:=[]
StartTime:=A_TickCount
Loop 1000000
  oProc1[A_Index]:=A_Index**8,oProc2[A_Index]:=A_Index**8

msgbox % "Elapsed Seconds using Main thread:" A_Tab (A_TickCount-StartTime)


  • Guests
  • Last active:
  • Joined: --
I see. Do you have any idea why the second script in my previous post crashes?

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
I am not sure :?
You should reuse your threads instead of starting a new thread each time tough :!:
[color=red]Loop 100 {
Loop 2
  Thread%A_Index%:=AhkDllThread(A_ScriptDir "\AutoHotkey.dll")[/color]
;...
;.......................Should be................
[color=blue]Loop 2
  Thread%A_Index%:=AhkDllThread(A_ScriptDir "\AutoHotkey.dll")
Loop 100 {[/color]
;...


kenn
  • Members
  • 407 posts
  • Last active: Jan 14 2015 08:16 PM
  • Joined: 11 Oct 2010
Nice topic. I have another question HotkeyIt, how can we use this example with CriticalSection instead of CriticalObject?
Edit: I added LowLevel_Init() and replaced CriticalObject with CriticalSection, I get "passing too many parameters" error.

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Hope this helps:
;----------------------- Create new CriticalObject
      obj:=CriticalObject()
   ;----------------------- equivalent with CriticalSection (not using CriticalObject)
      obj:={}
      CriSec:=CriticalSection()
;----------------------- Cal/Get/Set the CriticalObject
      obj.Call()
      obj.Get
      obj.Set:=value
   ;----------------------- equivalent with CriticalSection (not using CriticalObject)
      Lock(CriSec),   obj.Call(),   UnLock(CriSec)
      Lock(CriSec),   obj.Get,   UnLock(CriSec)
      Lock(CriSec),   obj.Set:=value,   UnLock(CriSec)
In AHK_H Lock and UnLock are Build In functions where in AHK_L you will need to use the ones posted in CriticalSection thread.

kenn
  • Members
  • 407 posts
  • Last active: Jan 14 2015 08:16 PM
  • Joined: 11 Oct 2010
Thank you for explanation HotKeyIt!

  • Guests
  • Last active:
  • Joined: --
If I compile the following script,
If 0
  FileInstall, AutoHotkey.dll, AutoHotkey.dll 
; ahkDll:=AhkDllObject( A_IsComiled ? "" : "AutoHotkey.dll")
ahkDll:=AhkDllObject()
ahkDll.ahktextdll() ;Starts empty thread in #Persistent + #NoTrayIcon mode
ahkDll.addScript("Sub:`nMsgbox Sub`nReturn",0) ;add but do not execute
MsgBox Script was added`nPress OK to GoSub
ahkDll.ahkLabel.Sub
I get
---------------------------
addscript.exe
---------------------------
Error:  Call to nonexistent function.

Specifically: AhkDllObject()

	Line#
--->	003: ahkDll := AhkDllObject()

The program will exit.
---------------------------
OK   
---------------------------
I used Ahk2Exe.exe which comes with the AutoHotkey_L installer. I specified AutoHotkeySC.bin of the Win32bin folder. Am I missing something?

kenn
  • Members
  • 407 posts
  • Last active: Jan 14 2015 08:16 PM
  • Joined: 11 Oct 2010
[color=red]#include AhkDllObject.ahk[/color]  
If 0
  FileInstall, AutoHotkey.dll, AutoHotkey.dll
;ahkDll:=AhkDllObject( A_IsComiled ? "" : "AutoHotkey.dll")
ahkDll:=AhkDllObject()
ahkDll.ahktextdll() ;Starts empty thread in #Persistent + #NoTrayIcon mode
ahkDll.addScript("Sub:`nMsgbox Sub`nReturn",0) ;add but do not execute
MsgBox Script was added`nPress OK to GoSub
ahkDll.ahkLabel.Sub
return
I just compiled with this

  • Guests
  • Last active:
  • Joined: --
That works but the file is in the library folder so isn't it supposed to be automatically included?

kenn
  • Members
  • 407 posts
  • Last active: Jan 14 2015 08:16 PM
  • Joined: 11 Oct 2010
I don't know how the compilation process works, I wonder that too.