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? 在界面线程里工作?