Page 1 of 1
Background processing
Posted: Thu Feb 13, 2020 8:35 am
by lesserpanda
Hi guys, is there a way to throw a procedure into the background and then trigger an event when it comes back with the results?
Much like Observables in JS. I'm writing an IMAP search for email and then do something, but the IMAP inbox is fairly large like 10GB, it takes time. I just want it to run, but come back with the results.
Also, I want to run several parallel procedures of the same thing. So one of them would be search for A, the other would be search for B.
Thanks
Re: Background processing
Posted: Thu Feb 13, 2020 8:38 am
by Demivec
You would use threads (and probably mutexes and semaphores) and PostEvent().
Re: Background processing
Posted: Thu Feb 13, 2020 8:59 am
by lesserpanda
Thanks for pointing me in the right direction. Much appreciated.
So this is what I'm doing
Create a thread with CreateThread() and push it back to the background. I use WaitThread() to wait for the thread to finish?
Question.
1. How do I know how many threads I can do before things go haywire? Like a limit?
2. So if I setup a button to run a procedure, thread it, is there an event which is triggered to let me know that it's done? Or is the WaitThread() the way to go for?
Thanks
Re: Background processing
Posted: Thu Feb 13, 2020 10:18 am
by BarryG
I don't think there's a thread limit, other than the amount of RAM on your PC.
Important: When using threads, make sure to ENABLE "Create threadsafe executable" in the Compiler Settings.
Here's a thread example:
Code: Select all
Procedure Background(null)
timeout.q=ElapsedMilliseconds()+2000
Repeat
SetWindowTitle(0,"Timeout: "+Str(Abs(ElapsedMilliseconds()-timeout)))
Delay(25)
Until ElapsedMilliseconds()>timeout
SetWindowTitle(0,"PureBasic")
DisableGadget(0, #False)
EndProcedure
If OpenWindow(0, 100, 200, 200, 50, "PureBasic", #PB_Window_SystemMenu)
ButtonGadget(0, 10, 10, 180, 25, "Click to start and wait for thread")
Repeat
Event = WaitWindowEvent()
If Event = #PB_Event_Gadget
DisableGadget(0, #True)
CreateThread(@Background(),0)
ElseIf Event = #PB_Event_CloseWindow
Quit = 1
EndIf
Until Quit = 1
EndIf
Re: Background processing
Posted: Thu Feb 13, 2020 10:31 am
by mk-soft
Changing the GUI does not work from threads. On Windows it works partially, and macOS and Linux will crash.
To change the GUI from threads, see signature ThreadToGUI (PostEvents)
Examples of thread management, see example
Mini Thread Control 
Re: Background processing
Posted: Thu Feb 13, 2020 10:56 am
by TI-994A
This example creates and executes two threads which simulate processing times of ten and twenty seconds respectively. Upon completion, each thread notifies the main thread via the
PostEvent() function, and passes along some numerical and string data as well. It also demonstrates the method to kill the threads gracefully, without using the
KillThread() function.
As BarryG has mentioned, be sure to select the
Create threadsafe executable option under the
Compiler -> Compiler Options menu. And as mk-soft has said, GUI elements should not be manipulated from within threads. Always post a corresponding event to the main thread and let it handle any and all GUI changes.
Code: Select all
; ensure that [Compiler > Compiler Options > Create threadsafe executable] is selected
#thread1CompleteEvent = #PB_Event_FirstCustomValue
#thread2CompleteEvent = #PB_Event_FirstCustomValue + 1
Global thread1Quit, thread2Quit
Procedure thread1(startTime)
; numerical value to be passed
eData = 1234567890
; simulate processing time (10 seconds)
While ElapsedMilliseconds() - startTime < 10000
; gracefully terminate thread if quit flag is set
If thread1Quit : Break : EndIf
Wend
; post completion event if thread not terminated
If Not thread1Quit
PostEvent(#thread1CompleteEvent, #PB_Ignore, #PB_Ignore, #PB_Ignore, eData)
EndIf
EndProcedure
Procedure thread2(startTime)
; string value to be passed - write into memory
*strPointer = AllocateMemory(100)
PokeS(*strPointer, "Hello, thread!")
; simulate processing time (20 secoonds)
While ElapsedMilliseconds() - startTime < 20000
; gracefully terminate thread if quit flag is set
If thread2Quit : Break : EndIf
Wend
; post completion event if thread not terminated
If Not thread2Quit
PostEvent(#thread2CompleteEvent, #PB_Ignore, #PB_Ignore, #PB_Ignore, *strPointer)
EndIf
EndProcedure
wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
If OpenWindow(0, #PB_Ignore, #PB_Ignore, 300, 180, "Thread Example", wFlags)
ResizeWindow(0, #PB_Ignore, WindowY(0) - 120, #PB_Ignore, #PB_Ignore)
TextGadget(0, 0, 20, 300, 30, "", #PB_Text_Center)
TextGadget(1, 0, 50, 300, 30, "", #PB_Text_Center)
ButtonGadget(2, 10, 90, 280, 30, "Kill Thread 1")
ButtonGadget(3, 10, 130, 280, 30, "Kill Thread 2")
startTime1 = ElapsedMilliseconds()
startTime2 = ElapsedMilliseconds()
testThread1 = CreateThread(@thread1(), startTime1)
testThread2 = CreateThread(@thread2(), startTime2)
; for displaying the countdown timers
AddWindowTimer(0, 0, 1000)
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
appQuit = 1
Case #PB_Event_Timer
counter1 = 10 - ((ElapsedMilliseconds() - startTime1) / 1000)
counter2 = 20 - ((ElapsedMilliseconds() - startTime2) / 1000)
If counter1 > -1 And IsThread(testThread1)
SetGadgetText(0, "Thread 1 completing in " + Str(counter1) + " seconds...")
EndIf
If counter2 > -1 And IsThread(testThread2)
SetGadgetText(1, "Thread 2 completing in " + Str(counter2) + " seconds...")
EndIf
Case #thread1CompleteEvent
SetGadgetText(0, "Thread 1 completed!")
MessageRequester("Thread 1 Complete:",
"Numerical data = " + Str(EventData()),
#PB_MessageRequester_Ok)
Case #thread2CompleteEvent
SetGadgetText(1, "Thread 2 completed!")
MessageRequester("Thread 2 Complete:",
"String data = " + PeekS(EventData()),
#PB_MessageRequester_Ok)
Case #PB_Event_Gadget
Select EventGadget()
Case 2
If IsThread(testThread1)
thread1Quit = #True
SetGadgetText(0, "Thread 1 terminated!")
EndIf
Case 3
If IsThread(testThread2)
thread2Quit = #True
SetGadgetText(1, "Thread 2 terminated!")
EndIf
EndSelect
EndSelect
Until appQuit
EndIf
Re: Background processing
Posted: Thu Feb 13, 2020 2:55 pm
by lesserpanda
Thank you for the examples. Understand the concepts.
Got a question ... for this set of code, AllocateMemory and PokeS
Code: Select all
*strPointer = AllocateMemory(100)
PokeS(*strPointer, "Hello, thread!")
What's would be the practical usage of this direct access?
Re: Background processing
Posted: Thu Feb 13, 2020 3:07 pm
by skywalk
The size and location of str pointer is controlled by you. Not the internal PB string library, which dynamically adjusts its memory and location. That is trickier to manage in dll and threads.
Re: Background processing
Posted: Thu Feb 13, 2020 3:18 pm
by TI-994A
lesserpanda wrote:...for this set of code, AllocateMemory and PokeS
Code: Select all
*strPointer = AllocateMemory(100)
PokeS(*strPointer, "Hello, thread!")
What's would be the practical usage of this direct access?
There is no direct way of passing data between a thread and the main program. However, the
PostEvent() function is able to send some numerical data through its event data parameter. This example shows how to use this
EventData() function to pass such values.
The first thread simply passes a numerical value to the main thread
(123345678890), while the second thread writes some string
(Hello, thread!) into memory, then passes the memory address
(*strPointer) to the main thread. The
AllocateMemory(),
PokeS() and
PeekS() functions, as used in the example, perform this task.
Alternatively, global variables could also be used to achieve this very same thing.
Re: Background processing
Posted: Thu Feb 13, 2020 7:50 pm
by mk-soft
@TI-994A
very nice memory leak. I don't see FreeMemory.
I like this with AllocateString and FreeString...
Code: Select all
; ensure that [Compiler > Compiler Options > Create threadsafe executable] is selected
CompilerIf #PB_Compiler_Thread = 0
CompilerError "Use Compiler Option ThreadSafe!"
CompilerEndIf
Enumeration CustomEvent #PB_Event_FirstCustomValue
#thread1CompleteEvent
#thread2CompleteEvent
EndEnumeration
Procedure AllocateString(String.s)
Protected *Memory.String = AllocateStructure(String)
If *Memory
*Memory\s = String
EndIf
ProcedureReturn *Memory
EndProcedure
Procedure.s FreeString(*Memory.String)
Protected r1.s
If *Memory
r1 = *Memory\s
FreeStructure(*Memory)
EndIf
ProcedureReturn r1
EndProcedure
Global thread1Quit, thread2Quit
Procedure thread1(startTime)
; numerical value to be passed
eData = 1234567890
; simulate processing time (10 seconds)
While ElapsedMilliseconds() - startTime < 10000
; gracefully terminate thread if quit flag is set
If thread1Quit : Break : EndIf
Wend
; post completion event if thread not terminated
If Not thread1Quit
PostEvent(#thread1CompleteEvent, #PB_Ignore, #PB_Ignore, #PB_Ignore, eData)
EndIf
EndProcedure
Procedure thread2(startTime)
; string value to be passed - write into memory
strData.s = "Hello, thread!"
; simulate processing time (20 secoonds)
While ElapsedMilliseconds() - startTime < 20000
; gracefully terminate thread if quit flag is set
If thread2Quit : Break : EndIf
Wend
; post completion event if thread not terminated
If Not thread2Quit
PostEvent(#thread2CompleteEvent, #PB_Ignore, #PB_Ignore, #PB_Ignore, AllocateString(strData))
EndIf
EndProcedure
wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
If OpenWindow(0, #PB_Ignore, #PB_Ignore, 300, 180, "Thread Example", wFlags)
ResizeWindow(0, #PB_Ignore, WindowY(0) - 120, #PB_Ignore, #PB_Ignore)
TextGadget(0, 0, 20, 300, 30, "", #PB_Text_Center)
TextGadget(1, 0, 50, 300, 30, "", #PB_Text_Center)
ButtonGadget(2, 10, 90, 280, 30, "Kill Thread 1")
ButtonGadget(3, 10, 130, 280, 30, "Kill Thread 2")
startTime1 = ElapsedMilliseconds()
startTime2 = ElapsedMilliseconds()
testThread1 = CreateThread(@thread1(), startTime1)
testThread2 = CreateThread(@thread2(), startTime2)
; for displaying the countdown timers
AddWindowTimer(0, 0, 1000)
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
appQuit = 1
Case #PB_Event_Timer
counter1 = 10 - ((ElapsedMilliseconds() - startTime1) / 1000)
counter2 = 20 - ((ElapsedMilliseconds() - startTime2) / 1000)
If counter1 > -1 And IsThread(testThread1)
SetGadgetText(0, "Thread 1 completing in " + Str(counter1) + " seconds...")
EndIf
If counter2 > -1 And IsThread(testThread2)
SetGadgetText(1, "Thread 2 completing in " + Str(counter2) + " seconds...")
EndIf
Case #thread1CompleteEvent
SetGadgetText(0, "Thread 1 completed!")
MessageRequester("Thread 1 Complete:",
"Numerical data = " + Str(EventData()),
#PB_MessageRequester_Ok)
Case #thread2CompleteEvent
SetGadgetText(1, "Thread 2 completed!")
MessageRequester("Thread 2 Complete:",
"String data = " + FreeString(EventData()),
#PB_MessageRequester_Ok)
Case #PB_Event_Gadget
Select EventGadget()
Case 2
If IsThread(testThread1)
thread1Quit = #True
SetGadgetText(0, "Thread 1 terminated!")
EndIf
Case 3
If IsThread(testThread2)
thread2Quit = #True
SetGadgetText(1, "Thread 2 terminated!")
EndIf
EndSelect
EndSelect
Until appQuit
EndIf
Re: Background processing
Posted: Fri Feb 14, 2020 1:50 am
by TI-994A
mk-soft wrote:very nice memory leak. I don't see FreeMemory...
Because it's not required in the context of this example.
All allocated memory blocks are automatically freed when the program ends.
So, no memory leak.

Re: Background processing
Posted: Fri Feb 14, 2020 9:49 am
by mk-soft
TI-994A wrote:mk-soft wrote:very nice memory leak. I don't see FreeMemory...
Because it's not required in the context of this example.
All allocated memory blocks are automatically freed when the program ends.
So, no memory leak.

In this context, maybe okay.
But you should already post memory safe examples
