win.involk设置线程超时时间

By popy32 at 2021-12-23 • 1人收藏 • 374人看过

编写多线程界面程序时,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有较大偏差


invokeAndWait2.gif


改进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*/);  
    }
    // 后面的代码省略


改进后的效果


invokeAndWait21.gif


效果比之前好了一些,和期望的timeout更接近,但窗口有太多消息时仍然会一直等待到消息结束,大家如果有更好的方案欢迎讨论。



2 个回复 | 最后更新于 2021-12-24
2021-12-23   #1

win.invoke带个win就是因为工作在窗口线程, 会被消息阻塞, 你这个时候就需要直接用thread.create, thread.wait就好了吧, 也不卡界面, 干嘛一定要用win.invoke? 在界面线程里工作?

2021-12-23   #2

回复#1 @admin :


文章里写了,win.invoke实际调用的时 thread.invokeAndWait,并不是窗口线程处理事情。


本文重点其实是想分享,线程超时退出的实现。标题不限于win.invoke设置超时,换成thread.involk,thread.create都可以。


另外thread.wait 测试中卡了界面 ,只能用thread.waitone,原理就在thread.threadwait这个函数。窗口事件,超时的实现都在thread.threadwait。

登录后方可回帖

登 录
信息栏
公告:
个人博客
专注分享
谢谢合作!

本站域名:HtmLayout.Cn
aardio可以快速开发上位机,本站主要记录了学习过程中遇到的问题和解决办法及aardio代码分享

这里主要专注于aardio学习交流和经验分享.
纯私人站,当笔记本用的,学到哪写到哪.

Aardio 官方站:Aardio官方
Aardio最新功能:Aardio官方更新日志
本 站 主 站:Stm32cube中文网
Sciter中文在线文档Sciter在线学习文档
空间赞助:才仁机械
打赏本站
Loading...