I am looking for a good explanation of the need for multithreading in graphical applications. The examples below use Python, but the question is not specific to python, it is applicable, perhaps, to the general design of graphic programming in any language.
Let's look at a simple example. Suppose there is an application that takes some time to work on a collection of files and displays it on the console. Suppose this operation takes 2 seconds per file and that there are 10 files to process, called 1.txt, 2.txt, 3.txt, ... 10.txt. Then an example implementation might look like this:
console
import time def process(file): print 'processing {0}...'.format(file) time.sleep(2.0)
The console example, of course, is single-threaded and works great. Now, if we want to add a graphical progress bar, a single-threaded implementation might look like this:
single threaded gui
import time, gtk, gobject def process(file): print 'processing {0}...'.format(file) time.sleep(2.0) class MainWindow(gtk.Window): def __init__(self): super(MainWindow, self).__init__() self.progress = gtk.ProgressBar() self.progress.set_fraction(0) self.add(self.progress) self.connect("destroy", gtk.main_quit) self.show_all() files = ['{0}.txt'.format(i) for i in range(1, 11)] gobject.timeout_add(100, self.submit, files, 0) def submit(self, files, i): process(files[i]) self.progress.set_fraction((i + 1.0)/len(files)) if i + 1 < len(files): gobject.idle_add(self.submit, files, i + 1) win = MainWindow() gtk.main()
This works fine, but when you start the application, if you are trying to interact with the application, for example, try resizing the window, for example, it will get stuck and will only respond every two seconds when it is released before processing pending gui events. The ultimate example is multi-threaded implementation and remains flexible at runtime.
multithreaded gui
import time, gtk, gobject, threading def process(file): print 'processing {0}...'.format(file) time.sleep(2.0) class MainWindow(gtk.Window): def __init__(self): super(MainWindow, self).__init__() self.progress = gtk.ProgressBar() self.progress.set_fraction(0) self.add(self.progress) self.connect("destroy", gtk.main_quit) self.show_all() files = ['{0}.txt'.format(i) for i in range(1, 11)] threading.Thread(target=self.submit, args=(files,)).start() def submit(self, files): for i, file in enumerate(files): process(file) gobject.idle_add(self.progress.set_fraction, (i + 1.0)/len(files)) if not self.get_visible(): return gtk.gdk.threads_init() win = MainWindow() gtk.main()
It seems perfectly clear and logical to me that if you have a long-term locking operation in your code and you want the flexible gui to use a multi-threaded solution. There is no other way. This is true? I tried to explain this many times to other developers, but many do not understand or disagree. Can someone give an explanation of this concept, a link to an article on it, or correct me if my understanding is incorrect.