Jump to content


[AHK.dll] Multi-Threading Basic Examples


  • Please log in to reply
70 replies to this topic

#1 Guests

  • Guests

Posted 15 January 2012 - 05:40 AM

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.

#2 HotKeyIt

HotKeyIt
  • Fellows
  • 6132 posts

Posted 17 January 2012 - 07:15 AM

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


#3 Guests

  • Guests

Posted 17 January 2012 - 10:42 AM

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.

#4 HotKeyIt

HotKeyIt
  • Fellows
  • 6132 posts

Posted 17 January 2012 - 12:27 PM

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 ;)

#5 Guests

  • Guests

Posted 18 January 2012 - 04:14 AM

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.

#6 HotKeyIt

HotKeyIt
  • Fellows
  • 6132 posts

Posted 18 January 2012 - 08:08 AM

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)


#7 Guests

  • Guests

Posted 18 January 2012 - 08:32 AM

I see. Do you have any idea why the second script in my previous post crashes?

#8 HotKeyIt

HotKeyIt
  • Fellows
  • 6132 posts

Posted 18 January 2012 - 08:41 AM

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]
;...


#9 kenn

kenn
  • Members
  • 407 posts

Posted 18 January 2012 - 08:49 AM

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.

#10 HotKeyIt

HotKeyIt
  • Fellows
  • 6132 posts

Posted 18 January 2012 - 11:54 AM

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.

#11 kenn

kenn
  • Members
  • 407 posts

Posted 18 January 2012 - 12:26 PM

Thank you for explanation HotKeyIt!

#12 Guests

  • Guests

Posted 19 January 2012 - 07:29 AM

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?

#13 kenn

kenn
  • Members
  • 407 posts

Posted 19 January 2012 - 09:35 AM

[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

#14 Guests

  • Guests

Posted 19 January 2012 - 11:29 AM

That works but the file is in the library folder so isn't it supposed to be automatically included?

#15 kenn

kenn
  • Members
  • 407 posts

Posted 19 January 2012 - 01:23 PM

I don't know how the compilation process works, I wonder that too.