tcl - 如果调用 tkwait / vwait,则 Tcl_DoOneEvent 被阻塞
问题描述
有一个从 Tcl/Tk 调用的外部 C++ 函数并在相当长的时间内完成了一些事情。Tcl 调用者必须得到该函数的结果,所以它一直等到它完成。为了避免阻塞 GUI,该 C++ 函数在其主体中实现了某种事件循环:
while (m_curSyncProc.isRunning()) {
const clock_t tm = clock();
while (Tcl_DoOneEvent(TCL_ALL_EVENTS | TCL_DONT_WAIT) > 0) {} // <- stuck here in case of tkwait/vwait
// Pause for 10 ms to avoid 100% CPU usage
if (double(clock() - tm) / CLOCKS_PER_SEC < 0.005) {
nanosleep(10000);
}
}
tkwait
除非/vwait
在 Tcl 代码中起作用,否则一切都很好。
例如,对于对话框,tkwait variable someVariable
用于等待Ok/Close/<whatever>
按钮被按下。我看到即使是标准 Tkbgerror
也使用相同的方法(它使用 vwait)。
问题是,当 Tcl 代码在 tkwait/vwait 行中等待时,一旦调用Tcl_DoOneEvent
就不会返回,否则它运行良好。是否可以在不完全重新设计 C++ 代码的情况下在该事件循环中修复它?因为该代码相当陈旧且复杂,并且其作者不再可访问。
解决方案
谨防!这是一个复杂的话题!
该Tcl_DoOneEvent()
调用本质上是什么vwait
,tkwait
并且update
是围绕的薄包装器(传递不同的标志并设置不同的回调)。对它们中的任何一个的嵌套调用都会创建嵌套事件循环;除非您非常小心,否则您并不真正想要那些。事件循环仅在它不处理任何活动的事件回调时终止,并且如果这些事件回调创建内部事件循环,则外部事件循环将在内部事件完成之前根本不会做任何事情。
当您控制外部事件循环时(以一种非常低效的方式,但是很好),您真的希望内部事件循环根本不运行。有三种可能的方法来处理这个问题;我怀疑第三个(协程)最适合您,而第一个是您真正想要避免的,但这绝对是您的决定。
1. 续传
您可以将内部代码重写为持续传递样式——一大堆程序通过状态机/工作流一步一步地传递——这样它就不会真正调用vwait
(和朋友)。该家族中唯一倾向于隐约安全的是update idletasks
(实际上只是Tcl_DoOneEvent(TCL_IDLE_EVENTS | TCL_DONT_WAIT)
)处理 Tk 内部生成的更改。
在 Tcl 8.5 之前,此选项是您的主要选择,而且工作量很大。
2. 线程
您可以移动到多线程应用程序。这可能很容易……也可能非常困难;细节取决于你在整个申请过程中所做的检查。
如果走这条路,请记住 Tcl 解释器和 Tcl 值完全是线程绑定的;他们在内部使用特定于线程的数据,这样他们就可以避免大的全局锁。这意味着 Tcl 中的线程设置起来相对昂贵,但之后实际上可以非常有效地使用多个 CPU;线程池是一种非常常见的方法。
3. 协程
从 8.6 开始,您可以将内部代码放在协程中。默认情况下,8.6 中的几乎所有内容都是协同程序感知的(在我们的内部术语中为“非递归”)(包括您通常不会想到的命令source
,vwait
例如从 Tcllib协程包中,事情通常会“正常工作”。(例如,vwait var
变成coroutine::vwait var
和after 123
变成coroutine::after 123
。)
唯一没有直接替换的东西是tkwait window
and tkwait visibility
; 你需要模拟那些等待一个<Destroy>
或<Visibility>
事件(后者不常见,因为它在某些平台上不受支持),你可以通过bind
对那些只设置一个你可以使用的变量coroutine::vwait
(本质上是无论如何,所有tkwait
内部操作)。
在某些情况下,协程可能会变得混乱,例如当您的 C 代码不支持协程时。这些在 Tcl 中发挥作用的主要地方是trace
回调、解释器间调用和通道的脚本实现;问题在于,这些位于后面的内部 API 已经相当复杂(尤其是通道),并且没有人愿意涉足并启用非递归实现。
推荐阅读
- azure - .NET 应用程序的 Azure ARM 模板部署
- string - 如何在 Go 中将整数转换为固定长度的十六进制字符串?
- c# - 在 try catch 块中重试异常
- c++ - 我在创建 Graph 实现时遇到问题
- php - 在 Laravel-excel 中访问 Excel::load 之外的变量
- php - 如何将上传的图像传输到目录以及将其复制到另一个目录
- android - 在 Android R 上通过 startActivity 查看 PDF 不起作用
- c# - 如何在 C# 中将 json 数据转换为数组。?
- node.js - 为部署在 GCP 中的 NodeJs 应用启用 cloudflare 代理时未触发 req.on('close') 事件
- ruby - 由于查询参数包括本地指定语言,所有使用辅助方法(如 user_path、usre_url)的测试都将失败