Using threads for online requests

maya
python

#1

In my tool I implement version check and auto-update functionality, so for that I need to send some GET requests to my server.

What I want to achieve is to make UI appear instantly, then check for updates in the background, and if it finds updates - present user with info box. Then if user clicks Update - run the updater window, which shows download and unzip progress, ideally without restricting user from using Maya while it happens. But it’s got to be a blocking process, so be it.

And I have a few slight issues with that.

First, what would be the best way to use threading for this matter? Or should I even bother?
So far using threads only brought instability in my scripts. I tried both QThreads and python threads, and they are not very stable, as in - sometimes maya can just crash. Even though I use try\except inside a thread process to ignore all errors, trying to ensure that if it can’t connect to the server it just ignores it. So it just makes my tools unusable for some people, unless they turn off checking for updates in the config file.

Another thing is updating PySide UI while downloading. I’m using chunked download with progress bar. Right now I managed to get it to work with simple synchronous approach with self.repain() and cmds.refresh() called on each status update. Which is fine for download, but it seems hacky to me, and ideally I’d like to avoid blocking maya while download is happening, to account for slow connections.

So, the question is - how should I approach implementing download threads and updating UI in Maya in the background?

Thanks.


#2

This won’t really answer any of your pyside related questions, but you might want to take a look at http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=Python_Python_and_threading.


#3

Generally you can’t touch anything in the maya scene or the maya UI from outside the main thread without trouble. Sometimes it works and sometimes it doesn’t, and it can work one minute and not the next. That’s the sad truth of Maya threading.

I’m not sure if this prohibition extends to home grown PyQT windows that aren’t part of the main maya UI but my guess would be that it does, since they are children of the main Maya window.

For a download style situation, you probably want to implement it two-step solution. Your downloader can update something – a Python queue is a good choice – as it goes and then the main thread can poll the queue to update UI or the scene. You’d have to implement the polling in a scriptJob – scriptJobs execute on the main thread so they will be safe, and it’s safe to access the queue outwards from the main thread. Unfortunately the obvious things you might try – like callbacks – usually fail because they cross thread boundaries.

For this sort of problem you might want to take a look at this project:

It implements a kind of thread-like system running cooperatively inside the main thread using a script job. This example:

Downloads sports scores of http and then displays them in a maya window without threading problems.


#4

Thanks for your replies, @R.White and @Theodox

Yes, I’ve read that, and noticed that I indeed was using some cmds.* funcitons which I did not even think about. cmds.about() for example. But I wonder why it worked for me most of the time, but did not work on other users’ PCs often.

So right now I reworked it for the version check, at least. What it does:

  1. My tool’s window is created and shown
  2. At the end of init it spawns a QThread which performs a version check, and then emits a True or False
  3. From now on main window\thread takes control again, and if False it presents user with a prompt window and well, after this everything is synchronous again.

So far it works on my machine. Wonder if it will work on others, huh. So it does not really touch anything from inside threads.

But now there’s no lag at startup of the UI, which was my main concern.

So, next goal is to rewrite downloader window to also use a download thread and emit progress into the main thread. I wonder if I can emit multiple times from a QThread, huh…


#5

Its been years, but from what I remember you should be able to emit a signal multiple times from a QThread.


#6

I use QThreads all the time in my UI’s and tools, they are super powerful because you can emit objects (or multiples), so it makes it easy to pass data around. Lots of times I have the QThread emit something like the current progress value to update a progress bar in the main app’s window. You can also initialize the progress bar by emitting a signal at the start of your operation and clean up the progress bar at the end of your op with another signal.

You can emit a custom signal as many times as you want inside the QThreads run function if you implement a loop in there. A QThread will stay alive as long as its run function is executing or you terminate it. If you want to do something like polling or interval updates, you can make a QTimer that either calls a function or spawns a thread. There are loads of examples on the web for using QThreads as workers for long running tasks.

The only real thing to watch out for with a QThread is its parent needs to be valid for the duration of the QThreads run - if you create a QThread with a parent widget that gets destroyed at the wrong moment it could crash your app for sure. Its usually easiest to set the QThreads parent to the instance of your application, something like QtGui.QApplication.instance() if you don’t have ready access to the qapp object. Also sometimes a Signal does not like to be instantiated with a custom data type, but i’ve gotten around this by having it just emit ‘object’ since python’s loose typing doesn’t care.

I’ve never tried modifying the Maya scene from a thread, and as others have said, I doubt that would work well. But you can give a QThread something to iterate over and have it emit data that you can handle in the main thread. As long as its not modifying the DAG.


#7

@leocov Thanks for your reply, it’s very helpful!

Well, so far, as documentation states, almost any cmds.* function that runs from a QThread kills Maya.
I somehow managed to make cmds.about() work with a simple Python thread, however, though it was unstable. In most cases it works, though in some specific cases it does not. It sometimes crashes randomly (rarely, but happens), and so far the only platform that reported 100% crashing was some home brewed version of Scientific Linux. I can’t be sure if crash was happening because of cmds.about or because it tried to access blocked internet (though it should just timeout).

So I’ll take it as a rule of thumb to never touch cmds.* from within a thread, and, I suppose, never touch any UI objects from within a thread as well.

When you talk about parent, you mean it’s better to set Maya main window as QThread parent? Or maya application?
If it’s application - how do I access an instance of Maya QApplication?


#8

As @leocov mentioned QtGui.QApplication.instance() will get you a reference to the application instance


#9

@R.White Oh, it’s that simple! Got it :slight_smile: Thanks!


#10

I made an example of using a QThread here:
maya-python-demos