Tkinter traffic-jamming - multithreading

I have an expensive function that is called via a Tkinter callback:
def func: # called whenever there is a mouse press in the screen.
print("Busy? " + str(X.busy)) # X.busy is my own varaible and is initialized False.
X.busy = True
do_calculations() # do_calculations contains several tk.Canvas().update() calls
X.busy = False
When I click too quickly, the func()'s appear to pile up because the print gives "Busy? True", indicating that the function hasen't finished yet and we are starting it on another thread.
However, print(threading.current_thread()) always gives <_MainThread(MainThread, started 123...)>, the 123... is always the same each print for a given program run. How can the same thread be multiple threads?

It looks to me like you're running into recursive message processing. In particular, tk.Canvas().update() will process any pending messages, including extra button clicks. Further, it will do this on the same thread (at least on Windows).
So your thread ID is constant, but your stack trace will have multiple nested calls to func.

Related

Can't update GtkIconView from within a click event on a GtkListBox in a GtkDialog

Here's what the click event currently looks like for the ListBox in the dialog code.
def OnSelectCategory(self, listbox, data=None):
try:
# Get the text of the selected category.
selected = listbox.get_selected_row()
label = selected.get_child()
itemText = label.get_text()
# Get the tags for the selected category.
tagtext = self.categoryTags.get(itemText)
self.updateStatusbar("Collecting videos...")
# Start a thread to scan for videos.
self.threadEvent = threading.Event()
self.videoscanThread = threading.Thread(target=self.ScanForVideos, args=(self.threadEvent, tagtext,))
self.videoscanThread.daemon = True
self.videoscanThread.start()
self.threadEvent.set()
except Exception as e:
print("Exception from 'OnSelectCategory':", str(e))
At first, I could not get the status bar to update the text immediately. I had originally called the function to update the text directly. The status bar text would not update until the ScanForVideos function had finished. So, I moved the ScanForVideos code into a thread. The thread waits on an event to begin.
The thread (ScanForVideos) runs several 'for' loops looking for a matching condition. When the condition is found, the code appends to the liststore for the IconView. At the end of the thread function, the code sets the IconView model to the liststore. The IconView seems to update with a few items, but, not all that should be there. Additionally, the code seems to be 'hung' because I cannot dismiss the dialog that contains the IconView. I have to stop debugging within Visual Studio Code.
I feel like I'm violating something I'm not aware of in Python coding. Or, my design to update the IconView is not correct. Can anyone shed some light on what I may be doing wrong?
I realized that I was trying to update some UI widgets from within my background thread. I then stumbled upon GLib.idle_add. I wrote a separate function to update the UI widgets and called idle_add passing the name of the function. This allowed me to update the GUI from a background thread.

PyQt5 UI stuck at long operation

I'm creating a game with some AI that may take some time. The problem is even if I call relevant methods to update the UI before running the AI function, the UI is not visually updated.
Some example code looks like this
def onClickBoard(self, e):
x, y = toBoardGrid(e.x(), e.y())
self.game.move(x, y)
self.update_board()
print("before AI")
# This line takes a few seconds
ai_move = self.ai.get_best_move(self.game)
print("after AI")
self.game.move(ai_move[0], ai_move[1])
self.update_board()
Where self.update_board is a method that updates a QWidget and it's very fast. This onClickBoard method is assigned to the widget's mouseReleaseEvent.
self.board.mouseReleaseEvent = self.onClickBoard
When running the game, I can see before AI printed to the terminal but the visual window doesn't change. I see the window updates only once, after the AI commits its move.
Is there a way to make the board update once before the slow function call and another once after it?
Yes, you can force Qt to process all pending events, and thus update the GUI, with the QApplication::processEvents() method. Add the following line just before the slow function call:
QtWidgets.QApplication.instance().processEvents()

Why seems my object to become NULL from one line to the next? - Could it be a hardware thing?

First thing to say: I program in a relatively unknown language: Blitzmax, which is a object oriented Basic dialect.
My problem is the following:
I wrote a debugmanager which runs in an own thread. So from every position in the program (it will be a game) you can add debug- or errormessages to the manager's queue.
In its own thread, it will fetch the messages from the queue and process them by writing them into a file and (if the message has the current chosen Debuglevel, Debugcategory and outputcategory, which are just enums) write it to the console.
Now I tested the program on three systems: My desktop PC which has Windows 8 as OS, my own laptop which has Windows 7 and the laptop of a friend which also has windows 7.
On my PC and my friend's laptop everything is fine.
But on my own laptop I get, nearly everytime, an "EXCEPTION_ACCESS_VIOLATION" Error while the manager is processing the messages.
Sometimes the program just runs fine, but most of the time it breaks down with this error.
Even in debugmode no line or stacktrace is shown which made it very hard to debug.
I broke all needed classes down to a minimum of attributes and functionality to make it easier to find the problem.
Now the queue is just a list (which is natively build in in Blitzmax) and the message just has one attribute which is a string.
Also the debugmanager only writes the message into the console without passing it to the process method which would write it to a file etc.
So the code which is actually needed is the following.
This is the message:
Type TThreadsafeMessage
Field complete_String:String
Method New_ThreadsafeMessage:TThreadsafeMessage(actual_Message:String, from_File:String, debugCategory:TDebugCategory_Enum, ..
debugLevel:TDebugLevel_Enum, outputCategory:TOutputCategory_Enum, from_Class:String = "", from_Method:String = "")
'Just create the string from the parameters.
Self.complete_String = actual_Message + " | " + from_File + "/" + from_Class + "/" + from_Method
Return Self
End Method
Method ToString:String()
'Just return the string attribute:
Return Self.complete_String' out_String
End Method
Method toString_Formatted_For_File:String()
Return Self.ToString()
End Method
Method toString_Formatted_For_Console:String()
Return Self.ToString()
End Method
End Type
This is the queue:
Type TThreadsafeQueue
'Predefined list.
Field list:TList
Method New()
Self.list = New TList
End Method
Method isEmpty:Byte()
Return Self.list.IsEmpty()
End Method
Method enqueue(to_Enqueue:Object)
'Add object to list
Self.list.AddLast(to_Enqueue)
End Method
Method dequeue:Object()
Return Self.list.RemoveFirst()
End Method
End Type
Here is the method which adds messages to the debugmanager:
Function enqueueMessage(message_To_Enqueue:TThreadsafeMessage)
'Test message for null pointer.
If(message_To_Enqueue = Null) Then
Throw New TNullpointer_Exception.NewException("'message_To_Enqueue' is NULL.", "TDebugmanager.bmx", ..
"TDebugmanager", "enqueueMessage")
EndIf
'Lock mutex for threadsafety.
LockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)
'Enqeue message in the queue
TDebugmanager.getSingleton_Instance().message_Queue.enqueue(message_To_Enqueue)
'Tell the update thread there is a message
SignalCondVar(TDebugmanager.getSingleton_Instance().sleep_ConditionVariable)
'Free the mutex for update thread.
UnlockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)
End Function
Now here is the (currently smaller) update function of the debugmanager:
Function _update:Object(thread_Object:Object)
'Do this over and over till the queue is empty AND the debugmanager is shut down
Repeat
Local message_To_Process:TThreadsafeMessage = Null
'Lock mutex for thread safety
LockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)
'Queue epmty...
If(TDebugmanager.getSingleton_Instance().message_Queue.isEmpty()) Then
'... Wait for a signal from the main thread
WaitCondVar(TDebugmanager.getSingleton_Instance().sleep_ConditionVariable, ..
TDebugmanager.getSingleton_Instance().queue_Mutex)
Else
'...Get the next message from the queue.
message_To_Process = TThreadsafeMessage(TDebugmanager.getSingleton_Instance().message_Queue.dequeue())
EndIf
'Unlock the mutex.
UnlockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)
'Check if the message is NULL.
If(message_To_Process = Null) Then
Throw "Got null message from queue."
EndIf
'Actually the _processMessage method is used. But for debugging
'it is commented out.
' TDebugmanager.getSingleton_Instance()._processMessage(message_To_Process)
'Write the message to the console.
'HERE is the error.
'See in the following description under the code section.
DebugLog("Message processed: " + message_To_Process.complete_String)
Until TDebugmanager.isFinished()
End Function
So at the position where it says in the update function 'HERE is the error.' the problem is the following: If this line is commented out or deleted, the program runs fine on my laptop and no error occures.
But if this line is there, the error occures most of the times.
An "EXCEPTION_ACCESS_VIOLATION" is thrown for example when: A stackoverflow occures somewhere. Or when you try to access to a NULL object.
Actually everything which tries to read or write from forbidden memory.
The really strange thing is: Only a few lines earlier, I check if the message which I got from the queue is NULL.
It should throw an error, as you can see.
But it never does.
Has anyone seen such a behaviour before?
I cannot explain that.
As I said: Debugging is really hard in this case. I could just break it down to smaller classes and finally the code you see here.
I also cannot just go step for step through the program with the debugger because then no error occures.
Can someone maybe think of something which can cause the error in this moment?
I know, this is much code, but I could not make it any shorter.
You are basically writing concurrently to a stream. If DebugLog is not reentrant (eg. there's some static buffer used internally), you will get random access violation. On faster machine, that could be perhaps avoided because contention will happen at the message queue, hence each thread will have the time to do a full loop on its own.
The solution is to lock the use of that function (you may want to make a function wrapper which implement that locking independently of the queue processing).
The problem was the following:
When I tried to debug the program a little bit more, sometimes the debugger kicked in and showed me where the debugger thought the error occured. It didn't occure there, but I was able to use the step-In function of the debugger.
The result was: It jumed into a library which is used for network methods. I do not use this library anywhere in the project.
So I tried a little bit further and the actual solution was: Deinstall Blitzmax and reinstall it. Now everything works fine.
Seems as if the linker was broken somehow.

Freeze when using tkinter + pyhook. Two event loops and multithreading

I am writing a tool in python 2.7 registering the amount of times the user pressed a keyboard or mouse button. The amount of clicks will be displayed in a small black box in the top left of the screen. The program registers clicks even when another application is the active one.
It works fine except when I move the mouse over the box. The mouse then freezes for a few seconds after which the program works again. If I then move the mouse over the box a second time, the mouse freezes again, but this time the program crashes.
I have tried commenting out pumpMessages() and then the program works. The problem looks a lot like this question pyhook+tkinter=crash, but no solution was given there.
Other answers has shown that there is a bug with the dll files when using wx and pyhook together in python 2.6. I don't know if that is relevant here.
My own thoughts is that it might have something to do with the two event loops running parallel. I have read that tkinter isn't thread safe, but I can't see how I can make this program run in a single thread since I need to have both pumpmessages() and mainlooop() running.
To sum it up: Why does my program freeze on mouse over?
import pythoncom, pyHook, time, ctypes, sys
from Tkinter import *
from threading import Thread
print 'Welcome to APMtool. To exit the program press delete'
## Creating input hooks
#the function called when a MouseAllButtonsUp event is called
def OnMouseUpEvent(event):
global clicks
clicks+=1
updateCounter()
return True
#the function called when a KeyUp event is called
def OnKeyUpEvent(event):
global clicks
clicks+=1
updateCounter()
if (event.KeyID == 46):
killProgram()
return True
hm = pyHook.HookManager()# create a hook manager
# watch for mouseUp and keyUp events
hm.SubscribeMouseAllButtonsUp(OnMouseUpEvent)
hm.SubscribeKeyUp(OnKeyUpEvent)
clicks = 0
hm.HookMouse()# set the hook
hm.HookKeyboard()
## Creating the window
root = Tk()
label = Label(root,text='something',background='black',foreground='grey')
label.pack(pady=0) #no space around the label
root.wm_attributes("-topmost", 1) #alway the top window
root.overrideredirect(1) #removes the 'Windows 7' box around the label
## starting a new thread to run pumMessages() and mainloop() simultaniusly
def startRootThread():
root.mainloop()
def updateCounter():
label.configure(text=clicks)
def killProgram():
ctypes.windll.user32.PostQuitMessage(0) # stops pumpMessages
root.destroy() #stops the root widget
rootThread.join()
print 'rootThread stopped'
rootThread = Thread(target=startRootThread)
rootThread.start()
pythoncom.PumpMessages() #pump messages is a infinite loop waiting for events
print 'PumpMessages stopped'
I've solved this problem with multiprocessing:
the main process handles the GUI (MainThread) and a thread that consumes messages from the second process
a child process hooks all mouse/keyboard events and pushes them to the main process (via a Queue object)
From the information that Tkinter needs to run in the main thread and not be called outside this thred, I found a solution:
My problem was that both PumpMessages and mainLoop needed to run in the main thread. In order to both receive inputs and show a Tkinter label with the amount of clicks I need to switch between running pumpMessages and briefly running mainLoop to update the display.
To make mainLoop() quit itself I used:
after(100,root.quit()) #root is the name of the Tk()
mainLoop()
so after 100 milliseconds root calls it's quit method and breaks out of its own main loop
To break out of pumpMessages I first found the pointer to the main thread:
mainThreadId = win32api.GetCurrentThreadId()
I then used a new thread that sends the WM_QUIT to the main thread (note PostQuitMessage(0) only works if it is called in the main thread):
win32api.PostThreadMessage(mainThreadId, win32con.WM_QUIT, 0, 0)
It was then possible to create a while loop which changed between pumpMessages and mainLoop, updating the labeltext in between. After the two event loops aren't running simultaneously anymore, I have had no problems:
def startTimerThread():
while True:
win32api.PostThreadMessage(mainThreadId, win32con.WM_QUIT, 0, 0)
time.sleep(1)
mainThreadId = win32api.GetCurrentThreadId()
timerThread = Thread(target=startTimerThread)
timerThread.start()
while programRunning:
label.configure(text=clicks)
root.after(100,root.quit)
root.mainloop()
pythoncom.PumpMessages()
Thank you to Bryan Oakley for information about Tkinter and Boaz Yaniv for providing the information needed to stop pumpMessages() from a subthread
Tkinter isn't designed to be run from any thread other than the main one. It might help to put the GUI in the main thread and put the call to PumpMessages in a separate thread. Though you have to be careful and not call any Tkinter functions from the other thread (except perhaps event_generate).

MFC dialog frozen

I need help how to unfreeze my dialog box. I'm using MFC and I have an infinite loop I want to execute when a button is pressed. However, the dialog box freezes when the infinite loop starts. Now I looked at this thread where someone was having a similar problem.
Unfortunately I tried multithreading but I found out that It can't work for me because I'm using an api that uses OLE automation and I'm getting an unhandled memory exception. I think this is because program uses the serial port and i read somewhere you can only use the handle to the serial port in one thread.
My program is simply to see if someone has dialed in to my modem and wait for them to send me a file, then hangup. Here is my loop.
while(1)
{
//get rid of input buffer
ts->_this->m_pHAScript->haReleaseRemoteInput();
ts-> _this->textBox->SetWindowTextA("thread Commence");
//wait for connected
if(success = ts->_this->m_pHAScript->haWaitForString("CONNECT",timeout))
{
//getFile
if(success = ts->_this->m_pHAScript->haWaitForXfer(5000))
{
//hangup
ts->_this->haTypeText("+++ath\r");
}
}
}
Is there a way to unfreeze the dialog box?
Add this code inside while loop:
MSG msg;
while(PeekMessage(&msg, GetSafeHwnd(), 0, 0, PM_REMOVE))
{
DispatchMessage(&msg);
}
The GUI in Windows relies on a message loop - somewhere in your code, either explicitly or hidden in a framework, there's a loop that checks for a message in a queue and processes it. If anything blocks the code from returning to that loop, the GUI gets frozen.
There are a few ways around this. One was given by David Brabant, essentially duplicating the loop. Another is to start a new "worker" thread that runs the blocking operation independently. If your message loop has a function that it calls when it is idle, i.e. no more messages are in the queue, you can do some processing there; that's not possible in your example however.

Resources