从 Tkinter Tcl 回调到 python 函数在 Windows 中崩溃 [英] Callback to python function from Tkinter Tcl crashes in windows

查看:53
本文介绍了从 Tkinter Tcl 回调到 python 函数在 Windows 中崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这不完全是我的应用程序,但非常相似.我创建了这个测试代码来显示问题.基本上我试图从 python 线程调用 tcl proc.当结果准备好时,Tcl proc 将回调到 python 函数.此结果将作为事件发布到 wx 框架.当我作为纯 python 代码运行时,它工作正常.当我使用 tcl proc 时,整个应用程序在没有任何信息的情况下崩溃.如果我增加 wait_time(比如 100),那么即使使用 tcl 也能正常工作.是回调率高是问题还是我错过了其他东西.顺便说一下,此应用在 Windows 上运行.

This is not exactly my application, but very similar. I have created this test code to show the problem. Basically I am trying to call tcl proc from python thread. Tcl proc will callback to python function when result is ready. This result will be posted as an event to wx frame. When I run as pure python code, it works fine. When I use tcl proc, the whole app crashes without any info. If I increase wait_time (say 100) then it works fine even with tcl. Is it the high rate of callback a problem or am I missing something else. This app runs on windows by the way.

import wx
from Tkinter import Tcl
from threading import Thread
import wx.lib.newevent
from time import sleep

CountUpdateEvent, EVT_CNT_UPDATE = wx.lib.newevent.NewEvent()

tcl_code = 'proc tcl_worker {name max_count delay_time callback} { while {$max_count>0} {after $delay_time; $callback $name $max_count; incr max_count -1}}'

# Option to use Tcl or not for counter
# When enabled, Tcl will callback to python to upate counter value
use_tcl = True

# Option to create tcl interpreter per thread. 
# Test shows single interpreter for all threads will fail.
use_per_thread_tcl = True 

count = 5000 
wait_time = 1 ;# in milliseconds

class Worker:
    def __init__(self,name,ui,tcl):
        global use_per_thread_tcl
        self.name = name
        self.ui = ui
        if use_per_thread_tcl:
            self.tcl = Tcl()
            self.tcl.eval(tcl_code)
        else:
            self.tcl = tcl
        self.target = ui.add_textbox(name)
        self.thread = Thread(target=self.run)
        self.thread.daemon = True
        self.thread.start()

    def callback(self, name, val):
        evt = CountUpdateEvent(name=self.name, val=val, target=self.target)
        wx.PostEvent(self.ui,evt)        
    def run(self):
        global count, wait_time, use_tcl

        if use_tcl:
            # Register a python function to be called back from tcl
            tcl_cmd = self.tcl.register(self.callback)

            # Now call tcl proc
            self.tcl.call('tcl_worker', self.name, str(count), str(wait_time), tcl_cmd)
        else:
            # Convert milliseconds to seconds for sleep
            py_wait_time = wait_time / 1000
            while count > 0:
                # Directly call the callback from here
                self.callback(self.name, str(count))
                count -= 1
                sleep(py_wait_time)


class MainWindow(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, title="Decrement Counter", size=(600, 100))

        self._DoLayout()
        self.Bind(EVT_CNT_UPDATE, self.on_count_update)

    def _DoLayout(self):
        self.sizer = wx.BoxSizer(wx.HORIZONTAL)

        self.panels = []
        self.tbs = []
        self.xpos = 0

    def add_textbox(self,name):
        panel = wx.Panel(self, pos=(self.xpos, 0), size=(60,40))
        self.panels.append(panel)
        tb = wx.StaticText(panel, label=name)
        tb.SetFont(wx.Font(16,wx.MODERN,wx.NORMAL,wx.NORMAL))
        self.sizer.Add(panel, 1, wx.EXPAND, 7)
        self.tbs.append(tb)
        self.xpos = self.xpos + 70
        return tb

    def on_count_update(self,ev):
        ev.target.SetLabel(ev.val)
        del ev

if __name__ == '__main__':
    app = wx.App(False)
    frame = MainWindow(None)
    tcl = Tcl()
    tcl.eval(tcl_code)
    w1 = Worker('A', frame, tcl)
    w2 = Worker('B', frame, tcl)
    w3 = Worker('C', frame, tcl)
    w4 = Worker('D', frame, tcl)
    w5 = Worker('E', frame, tcl)
    w6 = Worker('F', frame, tcl)
    w7 = Worker('G', frame, tcl)
    w8 = Worker('H', frame, tcl)
    frame.Show()
    app.MainLoop()

推荐答案

每个 Tcl 解释器对象(即知道如何运行 Tcl 过程的上下文)只能从创建它的 OS 线程中安全使用.这是因为 Tcl 不像 Python 那样使用全局解释器锁,而是广泛使用特定于线程的数据来减少内部所需的锁数量.(编写良好的 Tcl 代码可以利用这一点在合适的硬件上进行非常大的扩展.)

Each Tcl interpreter object (i.e., the context that knows how to run a Tcl procedure) can only be safely used from the OS thread that creates it. This is because Tcl doesn't use a global interpreter lock like Python, and instead makes extensive use of thread-specific data to reduce the number of locks required internally. (Well-written Tcl code can take advantage of this to scale up very large on suitable hardware.)

因此,您必须确保您只从单个线程运行 Tcl 命令或 Tkinter 操作;这通常是主线程,但我不确定这是否是与 Python 集成的真正要求.如果需要,您可以启动工作线程,但它们将无法使用 Tcl 或 Tkinter(当然,并非没有非常特殊的预防措施,这些预防措施比可能的价值更麻烦).相反,他们需要向主线程发送消息以处理与 GUI 的交互;有很多不同的方法可以做到这一点.

Because of this, you must make sure that you only ever run Tcl commands or Tkinter operations from a single thread; that's typically the main thread, but I'm not sure if that's the real requirement for integrating with Python. You can launch worker threads if you want, but they'll be unable to use Tcl or Tkinter (well, not without very special precautions which are more trouble than it's likely worth). Instead, they need to send messages to the main thread for it to handle the interaction with the GUI; there are many different ways to do that.

这篇关于从 Tkinter Tcl 回调到 python 函数在 Windows 中崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆