`
marlonyao
  • 浏览: 249120 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

wxpython中工作线程与主线程交互

阅读更多
在wxpython中,像其它GUI框架一样,如果要进行耗时很长的任务,需要将该任务放在工作线程中运行,以保证程序的响应性。工作线程可能会需要与主线程进行交互。这可以分为两种情况:一是工作线程只是向主线程单向通知工作进行的状态,比如任务完成进度,并不要求主线程回应。第二种情况是工作线程要调用主线程的方法,并且要求其返回,例如工作线程运行中需要弹出对话框让用户输入必要的信息(在wxpython中,显示对话框必须在主线程中进行)。

对于第一种情况,wxwiki已经谈了很多了,一种是工作进行使用wx.CallAfter主要调用主线程的方法,一种是工作线程发布事件(也是利用CallAfter),主线程监听该事件。第一种方法用起来要简单一些,但不如第二种方法灵活。对于第二种情况,wxwiki却很少谈到,网上也很少谈到其解决方法。不能使用CallAfter的原因在于CallAfter是异步调用,它只是将调用放在事件队列中,调用就返回了。如果我们想要等待调用在主线程中执行完毕再返回,就得使用别的方法。在Swing中存在InvokeAndWait就是为了达到此目的,在wxpython却不存在这样的方法,所幸很容易使用python的线程类Event达到类似的效果。

import wx
import threading
def call_and_wait(target, *args, **kwargs):
	if wx.Thread_IsMain():
		return target(*args, **kwargs)
	else:
		context = {}
		context['event'] = threading.Event()
		wx.CallAfter(call_in_mainthread, context, target, *args, **kwargs)
		context['event'].wait()
		return context['result']

def call_in_mainthread(context, target, *args, **kwargs):
	context['result'] = target(*args, **kwargs)
	context['event'].set()


主要思想是在工作线程创建一个Event对象,然后利用CallAfter放在主线程中去调用,工作线程调用event.wait()开始等待,当主线程执行完调用之后,设置event的状态,这样工作线程开始继续运行。下面是使用这个方法的一个例子:

class WorkThread(Thread):
	def run():
		# ...
		call_and_wait(ShowSomeDialog)

def ShowSomeDialog():
	# display some dialog
	dlg = UserPassDialg(None)
	if dlg.ShowModel() == wx.ID_OK:
		return (dlg.Username, dlg.Password)
	else:
		return None


call_and_wait调用很别扭,利用decorator我们可以取消该方法的调用,取而代之我们把它的调用放到decorator去做。

def mainthread(func, *args, **kwargs):
	'''ensure func invoked in main thread'''
	def _func():
		return call_and_wait(func, *args, **kwargs)
	return _func


这样我们可以将前面的例子改写成:

class WorkThread(Thread):
	def run():
		# ...
		ShowSomeDialog()

@mainthread
def ShowSomeDialog():
	# display some dialog
	dlg = UserPassDialg(None)
	if dlg.ShowModel() == wx.ID_OK:
		return (dlg.Username, dlg.Password)
	else:
		return None


似乎上面的代码并没有简洁多少,但是如果碰到需要大量调用主线程的方法时候,使用decorator可以避免创建许多一次性的方法。例如,在应用使用sqlite的情况下,由于sqlite只能单线程使用,所以也不能在工作线程中调用sqlite方法,通常的方法就是将所有sqlite调用都放到主线程中去做。

@mainthread
def get_account(id):
	#....

@mainthread
def save_account(account):
	#...

# more sqlite methods

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics