win.involk设置线程超时时间
编写多线程界面程序时,win.involk可以说是经常用到的函数,多线程带返回值,不卡界面。
import win.ui;
/*DSG{{*/
var winform = win.form(text="aardio form";right=319;bottom=159)
winform.add(
button={cls="button";text="访问网页";left=200;top=112;right=304;bottom=144;z=1};
edit={cls="edit";left=8;top=8;right=304;bottom=104;edge=1;multiline=1;z=2}
)
/*}}*/
showHttpData = function(){
var r = win.invoke(
function(){
import win;
import inet.http;
var url = "https://www.qq.com";
var ohttp = inet.http();
var d = ohttp.get(url);
var t = d;//web.json.tryParse(d);
return t;
}
)
if(r == null ) return "";
return r;
}
winform.button.oncommand = function(id,event){
winform.button.disabled = true;
winform.edit.text = showHttpData();
winform.button.disabled = false;
}
winform.show();
win.loopMessage();但有个需求是线程超时返回,例如访问网页指定超时退出(当然inet可以配置超时参数)。如果遇到线程卡住的时候
import win.ui;
/*DSG{{*/
var winform = win.form(text="aardio form";right=319;bottom=159)
winform.add(
button={cls="button";text="访问网页";left=200;top=112;right=304;bottom=144;z=1};
edit={cls="edit";left=8;top=8;right=304;bottom=104;edge=1;multiline=1;z=2}
)
/*}}*/
showHttpData = function(){
var r = win.invoke(
function(){
import win;
win.delay(4500);
return "4500ms";
}
)
if(r == null ) return "";
return r;
}
winform.button.oncommand = function(id,event){
winform.button.disabled = true;
winform.edit.text = showHttpData();
winform.button.disabled = false;
}
winform.show();
win.loopMessage();关注到thread.wait函数(lib/preload/thread.aardio)是自带超时退出的,于是试着给win.involk加上超时参数。
win.involk的实现
win.involk 实际调用的是 thread.invokeAndWait。相关的调用栈为
invokeAndWait -> waitOne -> threadwait
可以注意到threadwait实际上由超时处理的,只是invokeAndWait并没有传入超时参数,于是尝试重写invokeAndWait,增加参数超时时间timeout
namespace thread {
var _id_invoke = {};
invokeAndWait2 = function(timeout, func,... ){
var id = ..table.push(_id_invoke,1);
var name = ..string.format("$(_winvoke:[tid:%d][%d]",..thread.getId(),id );
var h = create(
function(func,name,...){
..thread.set(name, null);
var ret = { func(...) }
..thread.set(name, ret);
},func,name,...
);
if(timeout == 0) {
timeout = null;
}
waitOne(h, timeout);
//wait(h, timeout);
//waitClose(h, timeout);
var ret = get(name);
_id_invoke[id] = null;
..raw.closehandle(h);
if(ret){
return ..table.unpackArgs(ret);
}
}
}完整代码
import win.ui;
/*DSG{{*/
var winform = win.form(text="aardio form";right=319;bottom=159)
winform.add(
button={cls="button";text="访问网页";left=200;top=112;right=304;bottom=144;z=1};
edit={cls="edit";left=8;top=8;right=304;bottom=104;edge=1;multiline=1;z=2}
)
/*}}*/
namespace thread {
var _id_invoke = {};
invokeAndWait2 = function(timeout, func,... ){
var id = ..table.push(_id_invoke,1);
var name = ..string.format("$(_winvoke:[tid:%d][%d]",..thread.getId(),id );
var h = create(
function(func,name,...){
..thread.set(name, null);
var ret = { func(...) }
..thread.set(name, ret);
},func,name,...
);
if(timeout == 0) {
timeout = null;
}
waitOne(h, timeout);
//wait(h, timeout);
//waitClose(h, timeout);
var ret = get(name);
_id_invoke[id] = null;
..raw.closehandle(h);
if(ret){
return ..table.unpackArgs(ret);
}
}
}
import time.performance;
showHttpData = function(){
var tk1 = time.performance.tick();
var r = thread.invokeAndWait2( 500,
function(){
import win;
win.delay(4500);
return "";
}
)
var tk2 = time.performance.tick();
winform.edit.print( tostring(tk2 - tk1) ,"ms");
if(r == null ) return "";
return r;
}
winform.button.oncommand = function(id,event){
winform.button.disabled = true;
//winform.edit.text = "";
showHttpData()
//winform.edit.print( );
winform.button.disabled = false;
}
winform.show();
win.loopMessage();改进后的invokeAndWait2确实可以超时退出了,同时窗口也不会卡死,说明能响应窗口消息,但不停的拖放窗口,移动鼠标这些操作(相当于不停的发送)后发现,invokeAndWait2不能按照指定的超时时间timeout返回,需要等鼠标稳定后很长时间才能返回。
invokeAndWait2设置的是500ms,但实际和500ms有较大偏差

改进threadwait
继续研究threadwait的处理逻辑,threadwait主要过程是参数解析(获得需要wait的线程,手动自动handle),涉及消息处理的是API是 msgWaitForMultipleObjects,如果在threadwait过程中一种有窗口消息,流程将在do while中不断循环。
这时如果msgWaitForMultipleObjects以经过timeout超时后返回,又将在循环里继续运行,导致重复的timeout时间。处理方式也很简单,用last记录剩余的超时时间,time.performance计算实际运行的时间。
// 已修改过的threadwait
var threadwait = function( bClose,bAll, ...){
var threads,timeout = ...;
if(type(threads)!="table") {
timeout = 0xFFFFFFFF
threads ={...}
if( type(threads[#threads]) == "number" ){
timeout = ..table.pop(threads,1)
}
}
elseif(timeout === null ) timeout = 0xFFFFFFFF /* Infinite timeout*/
var len = #threads
if(!len) error("参数未指定线程句柄",3);
var threads_c = ..raw.toarray( threads ,"pointer" ,"array");
var msg,peek,parse,hasMsg;
var last = timeout; //剩余
var ret;
if( (!bAll) && ..win[["_form"]] ){
msg = ::MSG();
parse = ..win._parseMessage;
delay = ..win.delay;
peek = ..__messagePeek;
do{
var tk1 = ..time.performance.tick();
ret = msgWaitForMultipleObjects(len,threads_c,bAll,last , 0x4FF/*_QS_ALLINPUT*/);
//
/*
last = last - ..math.floor(tk2 - tk1);
if(last <= 0) {
break;
//..win.quitMessage(msg.wParam);
//return null;
}
*/
if( ret!=len ) break;
hasMsg = peek(msg);
if(hasMsg) {
parse(msg);
//delay(100);
}
elseif(hasMsg===null){
..win.quitMessage(msg.wParam);
}
var tk2 = ..time.performance.tick();
last = last - ..math.floor(tk2 - tk1);
if(last <= 0) {
break;
}
}while( hasMsg!==null)
}
else {
ret = waitForMultipleObjects(len,threads_c,bAll,timeout, 0x4FF/*_QS_ALLINPUT*/);
}
// 后面的代码省略改进后的效果

效果比之前好了一些,和期望的timeout更接近,但窗口有太多消息时仍然会一直等待到消息结束,大家如果有更好的方案欢迎讨论。
登录后方可回帖
win.invoke带个win就是因为工作在窗口线程, 会被消息阻塞, 你这个时候就需要直接用thread.create, thread.wait就好了吧, 也不卡界面, 干嘛一定要用win.invoke? 在界面线程里工作?