在紧密循环中处理窗口事件?
我已经为脚本语言编写了编译器和解释器。解释器是一个 DLL(“引擎”),它在单线程中运行,可以加载数百或数千个已编译的字节码应用程序,并将它们作为一组内部进程执行。有一个主循环,它在将每个加载的应用程序进程移动到下一个进程之前执行一些指令。
已编译应用程序中的字节码指令可以是低级指令(pop、push、add、sub 等),也可以是对外部函数库的调用(这是完成大部分工作的地方)。这些外部库可以回调引擎,将内部进程置于睡眠状态,等待特定事件,外部函数(可能在接收到事件后)将再次唤醒内部进程。如果所有内部进程都处于睡眠状态(大多数情况下),那么我也可以让引擎进入睡眠状态,从而将 CPU 移交给其他线程。
然而,没有什么可以阻止有人编写一个只执行如下紧密循环的脚本:
while(1) x=1; endwhile
这意味着我的主循环永远不会进入睡眠状态,因此 CPU 会上升到 100% 并锁定系统。我希望我的引擎尽可能快地运行,同时仍然处理窗口事件,以便其他应用程序在遇到类似于上述的紧密循环时仍然能够响应。
所以我的第一个问题是如何将代码添加到我的主循环中,以确保在不减慢主引擎速度的情况下处理 Windows 事件,主引擎应该以尽可能最快的速度运行。
此外,如果能够设置我的最大 CPU 使用率,那就太好了引擎可以通过偶尔调用 Sleep(1) 来使用和降低 CPU 使用率。
所以我的第二个问题是如何将 CPU 使用率降低到所需的水平?
该引擎是用 Borland C++ 编写的,并调用 win32 API。
提前致谢
I have written a compiler and interpreter for a scripting language. The interpreter is a DLL ('The Engine') which runs in a single thread and can load many 100s or 1000s of compiled byte-code applications and excecute them as a set of internal processes. There is a main loop that excecutes a few instructions from each of the loaded app processes before moving one to the next process.
The byte code instruction in the compiled apps can either be a low level instructions (pop, push, add, sub etc) or a call to an external function library (which is where most of the work is done). These external libararies can call back to the engine to put the internal processes into a sleep state waiting for a particular event upon which the external function (probably after receiving an event) will wake up the internal process again. If all internal processes are in a sleep state (which the are most of the time) then I can put the Engine to sleep as well thus handing off the CPU to other threads.
However there is nothing to prevent someone writing a script which just does a tight loop like this:
while(1)
x=1;
endwhile
Which means my main loop will never enter a sleep state and so the CPU goes up to 100% and locks up the system. I want my engine to run as fast as possibly, whilst still handling windows events so that other applications are still responsive when a tight loop similar to the above is encountered.
So my first question is how to add code to my main loop to ensure windows events are handled without slowing down the main engine which should run at the fastest speed possible..
Also it would be nice to be able to set the maximum CPU usage my engine can use and throttle down the CPU usage by calling the occasional Sleep(1)..
So my second question is how can I throttle down then CPU usage to the required level?
The engine is written in Borland C++ and makes calls to the win32 API.
Thanks in advance
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
1. 在运行脚本的同时运行消息循环
在执行另一个操作时继续运行消息循环的最佳方法是将另一个操作移至另一个线程。换句话说,将脚本解释器移至第二个线程,并从运行消息循环的主 UI 线程与其进行通信。
当你说 Borland C++ 时,我假设你正在使用 C++ Builder?在这种情况下,主线程是唯一与 UI 交互的线程,其消息循环通过
应用程序->运行
。如果您定期调用Application- >ProcessMessages
在你的库回调中,它是可重入的并且可能会导致问题。不要这样做。对您的问题的一条评论建议将每个脚本实例移至单独的线程。这将是理想的。但是,如果脚本调用的 DLL 保持状态,请注意这些问题 - DLL 是按进程加载的,而不是按线程加载的,因此,如果它们保持状态,您可能会遇到线程问题。目前纯粹为了解决您当前的问题,我建议将所有脚本执行移至另一个线程。
您可以通过多种方式在线程之间进行通信,例如使用 PostMessage 或 PostThreadMessage。由于您使用的是 Borland C++,因此您应该能够访问 VCL。它有一个很好的线程包装类,名为 TThread。由此派生并将脚本循环放入执行中。您可以使用同步 (块等待)或队列 (不阻塞;当目标线程处理其消息循环时,方法可以随时运行)在另一个线程的上下文中运行方法。
作为旁注:
这很奇怪。在现代的、抢占式多任务版本的 Windows 中,即使您的程序非常繁忙,其他应用程序仍应保持响应。您是否对线程优先级做了任何奇怪的事情,或者您是否使用了大量内存,以致其他应用程序被调出?
2. 处理脚本中的无限循环
您编写:
但将如何处理这个问题表述为:
我认为你采取了错误的方法。无限循环,如 while(1) x=1; endwhile 是脚本中的一个错误,但它不应该导致您的主机应用程序崩溃。仅限制 CPU 并不能让您的应用程序能够处理这种情况。 (使用大量 CPU 并不一定是一个问题:如果 CPU 可以运行该工作,那就执行它!只使用计算机的一小部分 CPU 并没有什么神圣的。它就在那里毕竟要使用。)(我认为)您真正想要的是能够在运行此脚本时继续让您的应用程序能够响应(由第二个线程解决),然后:
检测脚本何时“未响应”,或未调用回调
能够采取操作,例如询问用户是否要终止脚本script
执行此操作的另一个程序的示例是 Firefox。如果您转到包含行为不当脚本的页面,最终您会看到一个对话框,询问您是否要停止脚本运行。
如果不了解更多有关脚本实际如何解释或运行的信息,我无法对这两个问题给出详细的答案。但我可以建议一种方法,即:
您的解释器可能会运行一个循环,获取下一条指令并执行它。当前,您的交互性是通过从正在执行的指令之一运行的回调来提供的。我建议您通过让您的回调简单地记录上次调用的时间来利用它。然后在处理线程中,每条指令(或每十条或每一百条)检查当前时间与上次回调时间。如果已经过去了很长一段时间,例如十五秒或三十秒,则可能表明脚本被卡住了。通知主线程但继续处理。
对于“时间”,类似于 GetTickCount 可能就足够了。
下一步:您的主 UI 线程可以通过询问用户要做什么来对此做出反应。如果他们想要终止脚本,请与脚本线程通信以设置标志。在脚本处理循环中,再次每条指令(或数百条)检查此标志,如果已设置,则停止。
当您转向为每个脚本解释器配备一个线程时,您将使用
TThread
的Termminate
标志。按照惯例,对于在线程中无限运行的东西,您可以在Execute
函数中的while (!Termulated && [any other criteria])
循环中运行。 p>要真正回答有关使用更少 CPU 的问题,最好的方法可能是使用 将ThreadPriority设置为较低优先级,例如
THREAD_PRIORITY_BELOW_NORMAL
。如果没有其他需要运行,它仍然会运行。这将影响脚本的性能。另一种方法是使用 Sleep 作为你建议,但这确实是人为的。也许 SwitchToThread 稍微好一点 - 它屈服于操作系统选择的另一个线程。就我个人而言,我认为CPU是可以使用的,如果你解决了交互式UI的问题并处理失控的脚本,那么如果你的脚本需要它,使用所有CPU应该没有问题。如果您使用“太多”CPU,也许可以优化解释器本身。您需要运行分析器并找出 CPU 时间都花在哪里了。1. Running a message loop at the same time as running your script
The best way to continue running a message loop while performing another operation is to move that other operation to another thread. In other words, move your script interpreter to a second thread and communicate with it from your main UI thread, which runs the message loop.
When you say Borland C++, I assume you're using C++ Builder? In this situation, the main thread is the only one that interacts with the UI, and its message loop is run via
Application->Run
. If you're periodically callingApplication->ProcessMessages
in your library callbacks, that's reentrant and can cause problems. Don't do it.One comment to your question suggested moving each script instance to a separate thread. This would be ideal. However, beware of issues with the DLLs the scripts call if they keep state - DLLs are loaded per-process, not per-thread, so if they keep state you may encounter threading issues. For the moment purely to address your current question, I'd suggest moving all your script execution to a single other thread.
You can communicate between threads many ways, such as by posting messages between them using PostMessage or PostThreadMessage. Since you're using Borland C++, you should have access to the VCL. It has a good thread wrapper class called TThread. Derive from this and put your script loop in Execute. You can use Synchronize (blocks waiting) or Queue (doesn't block; method may be run at any time, when the target thread processes its message loop) to run methods in the context of another thread.
As a side note:
This is odd. In a modern, preemptively multitasked version of Windows other applications should still be responsive even when your program is very busy. Are you doing anything odd with your thread priorities, or are you using a lot of memory so that other applications are paged out?
2. Handling an infinite loop in a script
You write:
but phrase how to handle this as:
I think you're taking the wrong approach. An infinite loop like
while(1) x=1; endwhile
is a bug in the script, but it should not take down your host application. Just throttling the CPU won't make your application able to handle the situation. (And using lots of CPU isn't necessarily a problem: if it the work is available for the CPU to run, do it! There's nothing holy about using only a bit of your computer's CPU. It's there to use after all.) What (I think) you really want is to be able to continue to have your application able to respond when running this script (solved by a second thread) and then:Detect when a script is 'not responding', or not calling into your callbacks
Be able to take action, such as asking the user if they want to terminate the script
An example of another program that does this is Firefox. If you go to a page with a misbehaving script, eventually you'll get a dialog asking if you want to stop the script running.
Without knowing more about how your script is actually interpreted or run, I can't give a detailed answer to these two. But I can suggest an approach, which is:
Your interpreter probably runs a loop, getting the next instruction and executing it. Your interactivity is currently provided by a callback running from one of those instructions being executed. I'd suggest making use of that by having your callback simply log the time it was last called. Then in your processing thread, every instruction (or every ten or a hundred) check the current time against the last callback time. If a long time has passed, say fifteen or thirty seconds, it may be an indication that the script is stuck. Notify the main thread but keep processing.
For "time", something like GetTickCount is probably sufficient.
Next step: Your main UI thread can react to this by asking the user what to do. If they want to terminate the script, communicate with the script thread to set a flag. In your script processing loop, again every instruction (or hundred) check for this flag, and if it's set, stop.
When you move to having one thread per script interpreter, you
TThread
'sTerminated
flag for this. Idiomatically for something that runs infinitely in a thread, you run in awhile (!Terminated && [any other conditions])
loop in yourExecute
function.To actually answer your question about using less CPU, the best approach is probably to change your thread's priority using SetThreadPriority to a lower priority, such as
THREAD_PRIORITY_BELOW_NORMAL
. It will still run if nothing else needs to run. This will affect your script's performance. Another approach is to use Sleep as you suggest, but this really is artificial. Perhaps SwitchToThread is slightly better - it yields to another thread the OS chooses. Personally, I think the CPU is there to use, and if you solve the problem of an interactive UI and handling out-of-control scripts then there should be no problem with using all CPU if your script needs it. If you're using "too much" CPU, perhaps the interpreter itself could be optimised. You'll need to run a profiler and find out where the CPU time is being spent.尽管设计糟糕的脚本可能会让您陷入无所事事的循环,但不要担心。 Windows 就是为了处理这种事情而设计的,并且不会让您的程序占用超过其公平份额的 CPU。如果它确实达到了 100%,那只是因为没有其他东西想运行。
Although a badly designed script might put you in a do-nothing loop, don't worry about it. Windows is designed to handle this kind of thing, and won't let your program take more than its fair share of the CPU. If it does manage to get 100%, it's only because nothing else wants to run.