赞
踩
aardio 多线程开发入门
原贴地址:aardio 多线程开发入门
https://bbs.aardio.com/forum.php?mod=viewthread&tid=13625
(出处: aardio 官方社区)
| |
一个线程会排队执行一系列的编程指令,但一个线程同时只能做一件事。
例如在界面上有耗时的操作在执行时 - 就不能同时处理其他的界面消息或者响应用户的操作。
这时候我们就要使用多线程来完成我们的任务。
我们假设有一个耗时操作是这样的:
//下面这个函数执行耗时操作
doSomething = function( str ){
for(i=1;100){
str = str + " " + i;
sleep(100)
}
return 123;
}
一般我们直接调用这个函数会是这样写:
doSomething( "也可以有参数什么的" )
如果希望写复杂一点调用这个函数,我们也可以这样写:
invoke(doSomething ,,"也可以有参数什么的" )
如果我们希望创建一个新的线程来调用这个函数,那么就需要下面这样写:
thread.invoke(doSomething ,"也可以有参数什么的" )
切记不要犯一个低级错误:
如果把创建线程的代码改为 thread.invoke( doSomething("也可以有参数什么的") )
这是在创建线程前就调用函数了,实际执行的代码是 thread.invoke( 123 ) 这肯定会出错的。
aardio中多线程交换变量的几种方法:
1、如果你有一些函数需要被多个线程用到,请他们写到库文件里,然后在任何线程中使用 import 语句导入即可使用。
2、可以在创建线程时,通过线程的启动参数把变量从一个线程传入另一个线程,例如:
thread.invoke( 线程启动函数,"给你的","这也是给你的","如果还想要上车后打我电话" )
3、多线程共享的变量,必须通过 thread.get() 函数获取,并使用 thread.set() 函数修改其值,thread.var, thread.table 对这两个函数做了进一步的封装。
4、aardio提供了很多线程间相互调用函数的方法,通过这些调用方式的传参也可以交互变量,具体请查看aardio范例中的多线程范例。
界面线程会使用 win.loopMessage(); 启动一个消息循环,
win.loopMessage(); 就象一个快递公司不知疲倦的收发消息,直到最后一个非模态、非 MessageOnly 的独立窗口( 或 mainForm )关闭后才会退出消息循环。当然你也可以使用 win.quitMessage() 退出消息循环。
下面是一个启动界面线程的例子:
import win.ui;
/*DSG{{*/
var winform = win.form(text="aardio form";right=759;bottom=469)
winform.add(
button={cls="button";text="耗时操作";left=392;top=232;right=616;bottom=296;z=1}
)
/*}}*/
//用户点击窗口上的按钮时会触发下面的回调函数
winform.button.oncommand = function(id,event){
//下面用sleep函数休眠5秒(5000毫秒)模拟耗时操作
sleep(5000)
}
winform.show();
win.loopMessage();
复制上面的代码到 aardio 中并运行,你可以看到一个窗体显示在屏幕上。
如果你去掉代码中的最后一句 win.loopMessage() 那么窗体只会显示一下就消失了,你的程序也迅速退出了。
但如果你加上 win.loopMessage() 窗体就会一直显示在屏幕上(直到你点击关闭按钮)。并且你可以做其他的操作,例如点击按钮。
我们尝试点击按钮,点击按钮后触发了 winform.button.oncommand() 函数,一件让我们困惑的事发生了,窗体卡死了任何操作都没有反应,这是因为类似 sleep(5000) 这样的耗时操作阻塞了win.loopMessage()启动的消息循环过程。
一种解决方法是把 sleep(5000)改成 thread.delay(5000),虽然他们同样都是延时函数,但是 thread.delay() 在界面线程会继续处理窗口消息。但很多时候我们其他的耗时操作 —— 不能同时处理窗口消息,这时候就需要创建工作线程执行耗时操作。
下面的代码演示在界面线程中创建工作线程,然后在工作线程中与界面线程交互:
import win.ui;
/*DSG{{*/
var winform = win.form(text="多线程 —— 入门";right=536;bottom=325)
winform.add(
button={cls="button";text="启动线程";left=27;top=243;right=279;bottom=305;db=1;dl=1;dr=1;font=LOGFONT(h=-16);z=1};
edit={cls="edit";left=27;top=20;right=503;bottom=223;db=1;dl=1;dr=1;dt=1;edge=1;multiline=1;z=2}
)
/*}}*/
winform.button.oncommand = function(id,event){
//禁用按钮并显示动画
winform.button.disabledText = {"✶";"✸";"✹";"✺";"✹";"✷"}
//线程工作线程
thread.invoke(
function(winform){
for(i=1;3;1){
sleep(1000); //在界面线程执行 sleep 会卡住
//调用界面控件的成员函数 - 会转发到界面线程执行
winform.edit.print("工作线程正在执行,时间:" + tostring( time() ) );
}
winform.button.disabledText = null;
},winform //窗口对象可作为参数传入其他线程
)
}
winform.show();
win.loopMessage();
在工作线程中直接操作界面控件固然令人愉快,
但如果代码量一大,界面与逻辑混杂在一起,会让代码不必要的变的千头万绪复杂臃肿。
如果把多线程比作多条轨道上并列飞奔的火车,那么火车交互的方法不仅仅只有停下来同步,或者把手伸出车窗来个最直接的亲密交互。一种更好的方式是拿起手机给隔壁火车上的人打个电话 - 发个消息,或者等待对方操作完了再把消息发回来。
这种响应式的编程方式在aardio里就是 thead.command,下面我们看一个简单的例子:
import win.ui;
/*DSG{{*/
var winform = win.form(text="线程命令";right=599;bottom=399)
winform.add(
edit={cls="edit";left=12;top=11;right=588;bottom=389;db=1;dl=1;dr=1;dt=1;edge=1;multiline=1;z=1}
)
/*}}*/
import thread.command;
var listener = thread.command();
listener.print = function( ... ){
winform.edit.print( ... ) //我们在界面线程中这样响应工作线程的消息
}
//创建工作线程
thread.invoke(
function(){
//必须在线程函数内部导入需要的库
import thread.command;
//调用界面线程的命令
thread.command.print("hello world",1,2,3);
}
)
winform.show();
win.loopMessage();
thread.command可以把多线程间复杂的消息交互伪装成普通的函数调用,非常的方便。
这里新手仍然可能会困惑一点:我在工作线程中不是可以直接操作界面控件么?! 你这个thread.command虽然好用,但是多写了不少代码呀。
这样去理解是不对的,你开个轮船去对象菜市场买菜固然是有点麻烦,但如果你开轮船去环游世界那你就能感受到它的方便在哪里了。thread.command 一个巨大的优势是让界面与逻辑完全解耦,实现界面与逻辑的完全分离,当你的程序写到后面,代码越来越多,就能感受到这种模式的好处了,我举一个例子,例如 aardio自带的自动更新模块的使用示例代码:
import fsys.update.dlMgr;
var dlMgr = fsys.update.dlMgr("http://update.aardio.com/api/v1/version.txt","/download/update-files")
dlMgr.onError = function(err,filename){
//错误信息 err,错误文件名 filename 这里可以不用做任何处理,因为出错了就是没有升级包了
}
dlMgr.onConfirmDownload = function(isUpdated,appVersion,latestVersion,description){
if( ! isUpdated ){
//已经是最新版本了
}
else {
//检测到最新版本,版本号 latestVersion
};
return false; //暂不下载
}
dlMgr.create();
这个fsys.update.dlMgr里面就用到了多线程,但是他完全不需要直接操作界面控件。
而你在界面上使用这个对象的时候,你甚至都完全不用理会他是不是多线程,不会阻塞和卡死界面,有了结果你会收到通知,你接个电话就行了压根不用管他做了什么或者正在做什么。
这个fsys.update.dlMgr里面就是使用thread.command实现了实现界面与逻辑分离,你可以把检测、下载、更新替换并调整为不同的界面效果,但是fsys.update.dlMgr的代码可以始终复用。
我们有时候在界面中创建一个线程,仅仅是为了让界面不卡顿,我们希望用 thead.waitOne() 阻塞等待线程执行完闭(界面线程同时可以响应消息),然后我们又希望在后面关闭线程句柄,并获取到线程最后返回的值。
可能我们希望一切尽可能的简单,尽可能的少写代码,并且也不想用到thread.manage(因为并不需要管理多个线程)。
这时候我们可以使用 thread.invokeAndWait,thread.invokeAndWait 的参数和用法与 thread.invoke 完全一样,区别是 thread.invokeAndWait 会阻塞并等待线程执行完毕,并关闭线程句柄,同时获取到线程函数的返回值。
示例:
import win.ui;
/*DSG{{*/
var winform = win.form(text="aardio form";right=759;bottom=469)
winform.add(
button={cls="button";text="读取网页";left=272;top=368;right=624;bottom=440;z=1};
edit={cls="edit";text="edit";left=48;top=40;right=720;bottom=336;edge=1;multiline=1;z=2}
)
/*}}*/
winform.button.oncommand = function(id,event){
winform.button.disabledText = {"✶";"✸";"✹";"✺";"✹";"✷"}
winform.edit.text = thread.invokeAndWait(
function(winform){
sleep(3000);//暂停模拟一个耗时的操作
import inet.http;
return inet.http().get("http://www.aardio.com");
},winform
)
winform.button.disabledText = null;
}
winform.show()
win.loopMessage();
请复制上面的代码运行测试一下,在线程执行完以前,你仍然可以流畅的拖动窗口,操作界面。
一般我们可以使用 thread.invoke() 函数简单快捷的创建线程, //1、入门 |
aardio中提供了 thread.manage,thread.works 等用于管理多个线程的对象,
例如标准库中用于实现多线程多任务下载文件的 thread.dlManager 就使用了thread.works管理线程:
thread.works 用于创建多线程任务分派,多个线程执行相同的任务,但可以不停的分派新的任务,一个例子:
import console;
import thread.works;
var works = thread.works( 20,
function(...) {
import console;
thread.lock("写控制台")
console.log("线程ID" + thread.getId(),",开始工作,接收到任务指令参数",...)
thread.unlock("写控制台")
return "返回值,线程ID" + thread.getId();
}
);
//分派任务
works.push("一个任务")
works.push("两个任务")
//等待任务完成
works.wait(
function(r){
console.log( "检查成果", r )
}
)
works.push("三个任务")
works.push("四个任务")
works.push("五个任务")
//退出程序前,等待任务完成并关闭所有线程
works.waitClose(
function(r){
console.log( "检查成果", r )
}
)
execute("pause")
而 thread.manage 可以用来创建多个线程执行多个不同的任务,可以添加任意个线程启动函数,在线程执行完闭以后可以触发onEnd事件,并且把线程函数的返回值取回来,示例如下:
import console;
import thread.manage
//创建线程管理器
manage = thread.manage(3)
var thrdFunc = function(name){
import win;
import console;
for(i=1;10;1){
console.log( thread.getId(),name )
if( !win.delay(1000) ){ //主线程可以用 manage.quitMessage()中断这个循环
console.log("收到退出指令")
return;
}
}
return 67;
}
manage.create(thrdFunc,"线程1").onEnd = function(...){
console.log("线程1的回调",...)
}
manage.createLite(thrdFunc,"线程2").onEnd = function(){
console.log("线程2的回调")
}
manage.create(thrdFunc,"线程3")
manage.waitClose()
console.pause();
thread.manage通常是用于界面线程里管理工作线程,上面为了简化代码仅仅用到了控制台。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。