If you want to give a chance for GUI updates while you are doing processing then you need to call slicer.app.processEvents(), not just a sleep.
As you probably know, due to the global interpreter lock, there is no parallel execution of Python code within one interpreter, even if you start multiple threads. For IO operations, asynchronous IO might help, but I don’t know if anyone has figured out how to use that in Slicer.
A simpler solution is to start a separate process for background operations. Fortunately, Slicer already has a very simple solution for this: scripted CLI modules, which are Python scripts that are executed in the background, in a new Python interpreter. If you have a Python script that uploads an input image to a server then all you need to run it in the background in Slicer is to create a simple XML file that describes inputs and outputs of your script - see a complete example here. The XML file allows Slicer to recognize the script as a CLI module, and CLI modules are run in the background by default.
If you start multiple CLI modules in parallel then Slicer puts them in a queue and runs them one after the other.