Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[AHK_L][LIB]WorkerThread - easy and powerful Multithreading!


  • Please log in to reply
22 replies to this topic
fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009
Autohotkey unfortunately doesn't support real multithreading, so I wrote a library that makes multithreading possible by running multiple instances of the same script.

This library allows to pass object data to the worker instance (referred to as worker thread now). The worker thread can report its progress back to the main thread, and both can pause/resume/stop the worker thread, optionally with an arbitrary argument (e.g. a reason, can be an object). Arbitrary data can be send between the threads while the worker thread is running. If desired, the worker thread can stay running and wait for new tasks so it doesn't need to start again if a new task needs to be executed (memory usage vs. speed). The library uses event handlers (thanks IsNull!) and Serialization (thanks infogulch for LSON!).

Here's a simple example:
#SingleInstance, off ;required for obvious reasons
;This function needs to be called in the Autoexecute section so this instance
;can possibly turn into a worker thread. The worker thread stays in this function during its runtime.
InitWorkerThread()
;Main thread continues here
;Create the worker thread!
WorkerThread := new CWorkerThread("WorkerFunction", 0, 1, 1)

;Setup event handlers for the main thread
WorkerThread.OnStop.Handler := "OnStoppedByWorker"
WorkerThread.OnProgress.Handler := "ProgressHandler"
WorkerThread.OnFinish.Handler := "OnFinish"

;Start the worker thread
WorkerThread.Start("A Parameter")
return

;The functions below are event handlers of the main thread. They were specified above.
OnStoppedByWorker(WorkerThread, Result)
{
	Msgbox Error in worker thread! %Result%
	ExitApp
}

OnFinish(WorkerThread, Result)
{
	MsgBox Worker thread completed successfully! %Result%
	ExitApp
}

;Progress is a numeric integer value
ProgressHandler(WorkerThread, Progress)
{
	Tooltip, Progress: %Progress%
}


;This is the main worker function that is executed in the worker thread.
;The thread will exit shortly after this function returns.
;This function may have a many parameters as desired, but they need to be specified during the worker thread creation.
WorkerFunction(WorkerThread, Param)
{
	;This is a suggested structure for a worker thread that uses a loop.
	;It properly accounts for state changes (which can be caused by the main thread or this thread)
	while(A_Index <= 100 && WorkerThread.State = "Running")
	{
		Sleep 100 ;This simulates work that takes some time
		WorkerThread.Progress := A_Index ;Report the progress of the worker thread.
		
		;Lets allow this thread to randomly fail!
		Random, r, 1, 200
		if(r = 1)
			WorkerThread.Stop("Error: " r) ;Pass the error value to the main thread
	}
	;the return value of this function is only used when the worker thread wasn't stopped.
	return r
}

#include <WorkerThread>
And here's a detailed usage example:
#SingleInstance, off ;required for obvious reasons
;This function needs to be called in the Autoexecute section so this instance
;can possibly turn into a worker thread. The worker thread stays in this function during its runtime.
InitWorkerThread()
;Main thread continues here
Gui, Add, Progress, vProgressBar w400, 0
Gui, Add, Button, vMainStart gMainStart y+10, Start
Gui, Add, Button, vMainPause gMainPause x+10 w50 Disabled, Pause
Gui, Add, Button, vMainStop gMainStop x+10 Disabled, Stop
Gui, Add, Button, vMainData gMainData x+10 Disabled, Send Data

Gui, +LabelMainGUI
Gui, Show

;Create the worker thread! It will be reused in this program to demonstrate the possibility
;If the last parameter is set to 0, the worker thread will stay running and wait for tasks
WorkerThread := new CWorkerThread("WorkerFunction", 1, 1, 1)

;Setup event handlers for the main thread
WorkerThread.OnPause.Handler := "OnPausedByWorker"
WorkerThread.OnResume.Handler := "OnResumedByWorker"
WorkerThread.OnStop.Handler := "OnStoppedByWorker"
WorkerThread.OnData.Handler := "OnDataFromWorker"
WorkerThread.OnProgress.Handler := "ProgressHandler"
WorkerThread.OnFinish.Handler := "OnFinish"
return

MainGUIClose:
if(WorkerThread.State = "Running" || WorkerThread.State = "Paused")
	WorkerThread.Stop("Main thread exit") ;Stop the worker thread if it is still running
ExitApp

;The main thread can control the execution of the worker thread, demonstrated by the event handlers of the buttons below:
MainStart:
if(WorkerThread.State = "Stopped" || WorkerThread.State = "Finished")
{
	WorkerThread.Start("A Parameter", "Another unused parameter") ;Starting works only when in stopped state. The progress is reset to zero.
	GuiControl, Disable, MainStart
	GuiControl, Enable, MainStop
	GuiControl, Enable, MainPause
	GuiControl, Enable, MainData
	Gui, Show,, Running
}
return

MainPause:
if(WorkerThread.State = "Paused")
{
	WorkerThread.Resume()
	GuiControl, , MainPause, Pause
	Gui, Show,, Running
}
else if(WorkerThread.State = "Running")
{
	WorkerThread.Pause()
	GuiControl, , MainPause, Resume
	Gui, Show,,Paused by main thread
}
return

MainStop:
if(WorkerThread.State = "Running" || WorkerThread.State = "Paused")
{
	WorkerThread.Stop("Stop running, worker!") ;We can pass a reason for the stop to the worker thread
	GuiControl, Disable, MainStop
	GuiControl, Disable, MainPause
	GuiControl, Disable, MainData
	GuiControl, Enable, MainStart
	Gui, Show,,Stopped by main thread
}
return

MainData:
if(WorkerThread.State = "Running" || WorkerThread.State = "Paused")
	WorkerThread.SendData("Data from main thread") ;We can pass arbitrary data between the threads
return
;The functions below are event handlers of the main thread. They were specified above.
OnPausedByWorker(WorkerThread)
{
	global MainPause
	GuiControl, ,MainPause, Resume
	Gui, Show,, Paused by worker thread
}
OnResumedByWorker(WorkerThread)
{
	global MainPause
	GuiControl, ,MainPause, Pause
	Gui, Show,, Running
}
OnStoppedByWorker(WorkerThread, Result)
{
	global
	GuiControl, Enable, MainStart
	GuiControl, Disable, MainPause
	GuiControl, Disable, MainStop
	GuiControl, Disable, MainData
	Gui, Show,, Stopped by worker thread! Result: %Result%
}
OnFinish(WorkerThread, Result)
{
	global
	Gui, Show,, Finished! Result: %Result%
	GuiControl, Enable, MainStart
	GuiControl, Disable, MainPause
	GuiControl, Disable, MainStop
	GuiControl, Disable, MainData
}
OnDataFromWorker(WorkerThread, Data)
{
	MsgBox Data from worker: %Data%
}

;Progress is a numeric integer value
ProgressHandler(WorkerThread, Progress)
{
	global ProgressBar
	GuiControl, , ProgressBar, %Progress%
}


;This is the main worker function that is executed in the worker thread.
;The thread will exit shortly after this function returns.
;This function may have a many parameters as desired, but they need to be specified during the worker thread creation.
WorkerFunction(WorkerThread, Param)
{
	global WorkerProgress, WorkerPause, WorkerStop, WorkerData
	;We can set up some event handlers for the worker thread here
	;so it can react to pause/resume/stop events coming from the main thread
	WorkerThread.OnPause.Handler := "OnPausedByMain"
	WorkerThread.OnResume.Handler := "OnResumedByMain"
	WorkerThread.OnStop.Handler := "OnStoppedByMain"
	WorkerThread.OnData.Handler := "OnDataFromMain"
	Gui, Add, Progress, vWorkerProgress w400, 0
	Gui, Add, Button, vWorkerPause gWorkerPause w50, Pause
	Gui, Add, Button, vWorkerStop gWorkerStop x+10, Stop
	Gui, Add, Button, vWorkerData gWorkerData x+10, Send Data
	Gui, +LabelWorkerGUI
	Gui, Show,, Passed Parameter: %Param%
	;This is a suggested structure for a worker thread that uses a loop.
	;It properly accounts for state changes (which can be caused by the main thread or this thread)
	while(A_Index < 100 && WorkerThread.State = "Running")
	{
		GuiControl,,WorkerProgress, %A_Index%
		Sleep 40 ;This simulates work that takes some time
		WorkerThread.Progress := A_Index ;Report the progress of the worker thread.
		while(WorkerThread.State = "Paused") ;Optionally wait a while for resuming the worker thread.
			Sleep 10
	}
	Gui, Destroy
	;the return value of this function is only used when the worker thread wasn't stopped.
	return 42
}

;Prevent closing of the worker thread. Alternatively, the worker thread could stop itself.
WorkerGUIClose:
return

;The worker thread can control the execution of itself, demonstrated by the event handlers of the buttons below:
WorkerPause:
if(WorkerThread.State = "Paused")
{
	WorkerThread.Resume()
	GuiControl, , WorkerPause, Pause
}
else if(WorkerThread.State = "Running")
{
	WorkerThread.Pause()
	GuiControl, , WorkerPause, Resume
}
return

WorkerStop:
if(WorkerThread.State = "Running" || WorkerThread.State = "Paused")
{
	WorkerThread.Stop(23) ;Parameter is passed back to main thread as result
	Gui, Destroy
}
return

WorkerData:
if(WorkerThread.State = "Running" || WorkerThread.State = "Paused")
	WorkerThread.SendData("Data from worker thread!") ;We can send arbitrary data between threads!
return

;The functions below are event handlers of the worker thread. They were specified above.
OnPausedByMain()
{
	global WorkerPause
	GuiControl, , WorkerPause, Resume
	Gui, Show,, Paused by main thread
}
OnResumedByMain()
{	
	global WorkerPause
	GuiControl, , WorkerPause, Pause
	Gui, Show,, Running Worker thread
}
OnStoppedByMain(reason)
{
	Msgbox Stopped by main thread! Reason: %reason%
}
OnDataFromMain(Data)
{
	Msgbox Data from main thread: %Data%
}

#include <WorkerThread>
The library (together with this example and the required dependencies can be found on GitHub.

Here's a direct link due to a request: <!-- m -->https://github.com/C.../zipball/master<!-- m -->

nimda
  • Members
  • 4368 posts
  • Last active: Aug 09 2015 02:36 AM
  • Joined: 26 Dec 2010
Augh! The word "worker" looks weird now :p
Also, I think it's been done before (called "Instance()"?) but didn't support serialization. Still, you might be able to get a few ideas from the other library :)

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
Have you looked at message passing with zeromq ?
I wrote a thin wrapper for ahk : ahkzmq. With it you can do multithreading / communicate with python, mongrel2 etc in addition to autohotkey.

Also, you can already do simple interprocess communication between ahk scripts as com objects using dispatchobj.

gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
Instance() is similar but _Basic based, i never got around to adding WM_CopyData to send more than numbers interprocess, but i think it canned nicely, has no dependencies, and what you can pass to the worker via command line param is not limited to 8000 characters like the command prompt window

Link in my sig if you want ideas, but nice looking library
- gwarble

fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009
Thanks for your comments. I know that there are some alternatives around, but I wanted an AHK-only solution that is more advanced than current solutions. I'll see if I can make generic communication between the instances possible.

Banane
  • Members
  • 46 posts
  • Last active: Apr 07 2012 03:58 PM
  • Joined: 25 Nov 2009

I'll see if I can make generic communication between the instances possible

The easiest way would be an invisible Gui with a text control - every instance could write and read from it. :)

fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009
That's hardly a good solution though, more a kind of hack :p

  • Guests
  • Last active:
  • Joined: --
Can't you just post the whole thing as a zip file so people can test it?!
Its like I need 5 minutes of collecting JUST to test, sorry, I'm pissed.

gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
Instead of a copydata string message, can a pointer/address(/size) be shared between a two instances or are their memory seperate?

fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009

Can't you just post the whole thing as a zip file so people can test it?!
Its like I need 5 minutes of collecting JUST to test, sorry, I'm pissed.

There's a link to a zip file on the GitHub page I linked ("Zip" button on the left), please use that.
I'm not posting any packed files because I'm still working on the code so there will be frequent updates.

Instead of a copydata string message, can a pointer/address(/size) be shared between a two instances or are their memory seperate?

The memory between processes is usually not shared so this is not possible with normal means. It's possible to use RemoteBuffers though (search forum, there's a lib for it!).

gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
figured that would be too easy... then wm_copydata with an onmessage in each instance and a handler to direct the string to a user function/subroutine

nimda
  • Members
  • 4368 posts
  • Last active: Aug 09 2015 02:36 AM
  • Joined: 26 Dec 2010
Perhaps it's best to link to <!-- m -->https://github.com/C.../zipball/master<!-- m --> in the OP?

  • Guests
  • Last active:
  • Joined: --
This is neat. I would like to see a worker thread display a menu, then return the chosen menu item to the main script. Of course, the menu would be defined dynamically inside an object and would allow unlimited submenus...

fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009
I'm currently working on dynamic data passing between threads. There are some complications but I'll get it running soon (maybe tomorrow).

fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009
It's done! New features:

Passing arbitrary data between threads using WM_COPYDATA (no more temp file):

- On Task start (main->worker)
- On Stop (both directions)
- On Finish (worker->main)
- Custom data while worker thread is running (both directions)

Option to keep worker thread running and make it wait for new tasks

I think this should cover all use cases for multithreading. Please let me know if there are other things so I can expand this library. Also, please test and feel free to ask questions. I still have some documentation to write, but I wrote many comments so I hope that the relevant pieces are easy to find.