多线程
在 M8Test 中,每个脚本可创建多个子线程,但部分语言(如 PHP、Python、Ruby)的脚本除外。
每个脚本运行时,都会先创建一个脚本线程来执行脚本代码,这是脚本创建的第一个线程,称为主线程;后续通过 Threads.start 方法在脚本中创建的线程则为子线程。
脚本主线程与Android的UI线程类似,建议不要阻塞脚本主线程,因为它会涉及一些UI操作(例如脚本端与网页交互时,回调函数需在脚本主线程中执行)。
脚本主线程会在脚本中其他所有子线程停止后才停止。脚本与线程的关系为一对多,不支持多线程的语言(如 PHP、Python、Ruby)则为一对一。
定时器
定时器用于添加定时任务,每个线程都有且仅有一个定时器。请注意,只有通过 Threads.start 创建的线程才具备定时器,可通过 getTimer 方法获取。
定时器添加的任务将按时间顺序执行,且仅当前一个任务执行完毕后,才会执行下一个任务,所有任务均在定时器所属的线程中运行, 只有所有定时任务执行完成后,脚本线程才会停止。
当然,若你使用 setBackground 设置线程在后台运行,那么即使所有任务执行完毕,脚本线程也不会停止,除非再次调用 setBackground 取消后台运行状态。
import com.m8test.script.GlobalVariables._console
import com.m8test.script.GlobalVariables._threads
// 脚本主线程,也就是脚本第一个启动的线程,该线程会在所有当前脚本的其他线程结束后才会结束
// 请不要在脚本主线程执行阻塞任务,可以类比于android的ui线程
val mainThread = _threads.getMain()
// 获取线程定时器,可以通过定时器添加定时任务, 注意,定时器启动的定时任务不会阻塞脚本线程,推荐使用定时器添加任务
// 1. setTimeout() : 延时任务,只执行一次,可以指定延时时间
// 2. setInterval() : 周期任务,会重复执行,可以指定执行周期
// 3. setImmediate() : 立即执行任务,只执行一次
// 通过线程定时器添加的定时任务会在该线程运行,也就是说添加的定时任务会按照执行的时间顺序执行,只有前一个定时任务执行完毕时才会执行下一个定时任务
val mainTimer = mainThread.getTimer()
// 添加一个立即执行的定时任务,但是该任务添加后依旧不会被执行,只有前一个任务执行完成后才会执行该任务
mainTimer.setImmediate({
_console.log("2 主线程添加的定时任务")
})
var intervalId = -1
var times = 5
intervalId = mainTimer.setInterval({
if (times <= 0) {
_console.log("4 结束运行周期任务")
mainTimer.clearInterval(intervalId)
} else {
_console.log("3 周期任务执行", times)
}
times--
}, 1000)
// 实际上脚本执行时就是启动一个立即执行的定时任务来执行代码的
_console.log("1 主线程启动的第一个定时任务")
// 脚本主线程,也就是脚本第一个启动的线程,该线程会在所有当前脚本的其他线程结束后才会结束
// 请不要在脚本主线程执行阻塞任务,可以类比于android的ui线程
def mainThread = $threads.getMain()
// 获取线程定时器,可以通过定时器添加定时任务, 注意,定时器启动的定时任务不会阻塞脚本线程,推荐使用定时器添加任务
// 1. setTimeout() : 延时任务,只执行一次,可以指定延时时间
// 2. setInterval() : 周期任务,会重复执行,可以指定执行周期
// 3. setImmediate() : 立即执行任务,只执行一次
// 通过线程定时器添加的定时任务会在该线程运行,也就是说添加的定时任务会按照执行的时间顺序执行,只有前一个定时任务执行完毕时才会执行下一个定时任务
def mainTimer = mainThread.getTimer()
// 添加一个立即执行的定时任务,但是该任务添加后依旧不会被执行,只有前一个任务执行完成后才会执行该任务
mainTimer.setImmediate({
$console.log("2 主线程添加的定时任务")
})
int intervalId = -1
int times = 5
intervalId = mainTimer.setInterval({
if (times <= 0) {
$console.log("4 结束运行周期任务")
mainTimer.clearInterval(intervalId)
} else {
$console.log("3 周期任务执行", times)
}
times--
}, 1000)
// 实际上脚本执行时就是启动一个立即执行的定时任务来执行代码的
$console.log("1 主线程启动的第一个定时任务")
import com.m8test.script.core.api.thread.NewScriptThread;
import com.m8test.script.core.api.thread.Timer;
import kotlin.jvm.functions.Function1;
import static com.m8test.script.GlobalVariables.$console;
import static com.m8test.script.GlobalVariables.$threads;
// 脚本主线程,也就是脚本第一个启动的线程,该线程会在所有当前脚本的其他线程结束后才会结束
// 请不要在脚本主线程执行阻塞任务,可以类比于android的ui线程
NewScriptThread mainThread = $threads.getMain();
// 获取线程定时器,可以通过定时器添加定时任务, 注意,定时器启动的定时任务不会阻塞脚本线程,推荐使用定时器添加任务
// 1. setTimeout() : 延时任务,只执行一次,可以指定延时时间
// 2. setInterval() : 周期任务,会重复执行,可以指定执行周期
// 3. setImmediate() : 立即执行任务,只执行一次
// 通过线程定时器添加的定时任务会在该线程运行,也就是说添加的定时任务会按照执行的时间顺序执行,只有前一个定时任务执行完毕时才会执行下一个定时任务
final Timer mainTimer = mainThread.getTimer();
// 添加一个立即执行的定时任务,但是该任务添加后依旧不会被执行,只有前一个任务执行完成后才会执行该任务
mainTimer.setImmediate(new Function1() {
@Override
public Object invoke(Object o) {
$console.log("2 主线程添加的定时任务");
return null;
}
});
final int[] intervalId = {-1}; // 使用数组来包装 intervalId
final int[] times = {5}; // 使用数组来包装 times
intervalId[0] = mainTimer.setInterval(new Function1() {
@Override
public Object invoke(Object objects) {
if (times[0] <= 0) {
$console.log("4 结束运行周期任务");
mainTimer.clearInterval(intervalId[0]);
} else {
$console.log("3 周期任务执行", times[0]);
}
times[0]--;
return null;
}
}, 1000);
// 实际上脚本执行时就是启动一个立即执行的定时任务来执行代码的
$console.log("1 主线程启动的第一个定时任务");
// 脚本主线程,也就是脚本第一个启动的线程,该线程会在所有当前脚本的其他线程结束后才会结束
// 请不要在脚本主线程执行阻塞任务,可以类比于android的ui线程
let mainThread = $threads.getMain()
// 获取线程定时器,可以通过定时器添加定时任务, 注意,定时器启动的定时任务不会阻塞脚本线程,推荐使用定时器添加任务
// 1. setTimeout() : 延时任务,只执行一次,可以指定延时时间
// 2. setInterval() : 周期任务,会重复执行,可以指定执行周期
// 3. setImmediate() : 立即执行任务,只执行一次
// 通过线程定时器添加的定时任务会在该线程运行,也就是说添加的定时任务会按照执行的时间顺序执行,只有前一个定时任务执行完毕时才会执行下一个定时任务
let mainTimer = mainThread.getTimer()
// 添加一个立即执行的定时任务,但是该任务添加后依旧不会被执行,只有前一个任务执行完成后才会执行该任务
mainTimer.setImmediate(function (p) {
$console.log("2 主线程添加的定时任务")
})
let intervalId = -1
let times = 5
intervalId = mainTimer.setInterval(function (p) {
if (times <= 0) {
$console.log("4 结束运行周期任务")
mainTimer.clearInterval(intervalId)
} else {
$console.log("3 周期任务执行", times)
}
times--
}, 1000)
// 实际上脚本执行时就是启动一个立即执行的定时任务来执行代码的
$console.log("1 主线程启动的第一个定时任务")
-- 脚本主线程,也就是脚本第一个启动的线程,该线程会在所有当前脚本的其他线程结束后才会结束
-- 请不要在脚本主线程执行阻塞任务,可以类比于android的ui线程
local mainThread = _threads:getMain()
-- 获取线程定时器,可以通过定时器添加定时任务, 注意,定时器启动的定时任务不会阻塞脚本线程,推荐使用定时器添加任务
-- 1. setTimeout() : 延时任务,只执行一次,可以指定延时时间
-- 2. setInterval() : 周期任务,会重复执行,可以指定执行周期
-- 3. setImmediate() : 立即执行任务,只执行一次
-- 通过线程定时器添加的定时任务会在该线程运行,也就是说添加的定时任务会按照执行的时间顺序执行,只有前一个定时任务执行完毕时才会执行下一个定时任务
local mainTimer = mainThread:getTimer()
-- 添加一个立即执行的定时任务,但是该任务添加后依旧不会被执行,只有前一个任务执行完成后才会执行该任务
mainTimer:setImmediate(function(p)
_console:log("2 主线程添加的定时任务")
end)
local intervalId = -1
local times = 5
intervalId = mainTimer:setInterval(function(p)
if (times <= 0) then
_console:log("4 结束运行周期任务")
mainTimer:clearInterval(intervalId)
else
_console:log("3 周期任务执行", times)
end
times = times - 1
end, 1000)
-- 实际上脚本执行时就是启动一个立即执行的定时任务来执行代码的
_console:log("1 主线程启动的第一个定时任务")
<?php
// 脚本主线程,也就是脚本第一个启动的线程,该线程会在所有当前脚本的其他线程结束后才会结束
// 请不要在脚本主线程执行阻塞任务,可以类比于android的ui线程
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
$mainThread = $threads->getMain();
// 获取线程定时器,可以通过定时器添加定时任务, 注意,定时器启动的定时任务不会阻塞脚本线程,推荐使用定时器添加任务
// 1. setTimeout() : 延时任务,只执行一次,可以指定延时时间
// 2. setInterval() : 周期任务,会重复执行,可以指定执行周期
// 3. setImmediate() : 立即执行任务,只执行一次
// 通过线程定时器添加的定时任务会在该线程运行,也就是说添加的定时任务会按照执行的时间顺序执行,只有前一个定时任务执行完毕时才会执行下一个定时任务
$mainTimer = $mainThread->getTimer();
// 添加一个立即执行的定时任务,但是该任务添加后依旧不会被执行,只有前一个任务执行完成后才会执行该任务
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
$mainTimer->setImmediate(function () use ($console) {
$console->log(javaString("2 主线程添加的定时任务"));
});
$intervalId = -1;
$times = 5;
$intervalId = $mainTimer->setInterval(function () use (&$times, $console, &$intervalId, $mainTimer) {
if ($times <= 0) {
$console->log(javaString("4 结束运行周期任务"));
$mainTimer->clearInterval($intervalId);
} else {
$console->log(javaString("3 周期任务执行"), $times);
}
$times--;
}, 1000);
// 实际上脚本执行时就是启动一个立即执行的定时任务来执行代码的
$console->log(javaString("1 主线程启动的第一个定时任务"));
from m8test_java.com.m8test.script.GlobalVariables import _console
from m8test_java.com.m8test.script.GlobalVariables import _threads
# 脚本主线程,也就是脚本第一个启动的线程,该线程会在所有当前脚本的其他线程结束后才会结束
# 请不要在脚本主线程执行阻塞任务,可以类比于android的ui线程
mainThread = _threads.getMain()
# 获取线程定时器,可以通过定时器添加定时任务, 注意,定时器启动的定时任务不会阻塞脚本线程,推荐使用定时器添加任务
# 1. setTimeout() : 延时任务,只执行一次,可以指定延时时间
# 2. setInterval() : 周期任务,会重复执行,可以指定执行周期
# 3. setImmediate() : 立即执行任务,只执行一次
# 通过线程定时器添加的定时任务会在该线程运行,也就是说添加的定时任务会按照执行的时间顺序执行,只有前一个定时任务执行完毕时才会执行下一个定时任务
mainTimer = mainThread.getTimer()
# 添加一个立即执行的定时任务,但是该任务添加后依旧不会被执行,只有前一个任务执行完成后才会执行该任务
mainTimer.setImmediate(lambda p: _console.log("2 主线程添加的定时任务"))
intervalId = -1
times = 5
def fn(p):
global times
if times <= 0:
_console.log("4 结束运行周期任务")
mainTimer.clearInterval(intervalId)
else:
_console.log("3 周期任务执行", times)
times = times - 1
intervalId = mainTimer.setInterval(fn, 1000)
# 实际上脚本执行时就是启动一个立即执行的定时任务来执行代码的
_console.log("1 主线程启动的第一个定时任务")
# encoding: utf-8
# 脚本主线程,也就是脚本第一个启动的线程,该线程会在所有当前脚本的其他线程结束后才会结束
# 请不要在脚本主线程执行阻塞任务,可以类比于android的ui线程
mainThread = $threads.getMain()
# 获取线程定时器,可以通过定时器添加定时任务, 注意,定时器启动的定时任务不会阻塞脚本线程,推荐使用定时器添加任务
# 1. setTimeout() : 延时任务,只执行一次,可以指定延时时间
# 2. setInterval() : 周期任务,会重复执行,可以指定执行周期
# 3. setImmediate() : 立即执行任务,只执行一次
# 通过线程定时器添加的定时任务会在该线程运行,也就是说添加的定时任务会按照执行的时间顺序执行,只有前一个定时任务执行完毕时才会执行下一个定时任务
mainTimer = mainThread.getTimer()
# 添加一个立即执行的定时任务,但是该任务添加后依旧不会被执行,只有前一个任务执行完成后才会执行该任务
mainTimer.setImmediate(lambda { |p|
$console.log("2 主线程添加的定时任务")
})
intervalId = -1
times = 5
intervalId = mainTimer.setInterval(lambda { |p|
if times <= 0
$console.log("4 结束运行周期任务")
mainTimer.clearInterval(intervalId)
else
$console.log("3 周期任务执行", times)
times = times - 1
end
}, 1000)
# 实际上脚本执行时就是启动一个立即执行的定时任务来执行代码的
$console.log("1 主线程启动的第一个定时任务")

创建子线程
M8Test执行脚本时可创建多个子线程,但部分语言(如PHP、Python、Ruby)不支持多线程。若在这些不支持多线程的语言中尝试创建子线程,很可能会导致脚本崩溃。
import com.m8test.script.GlobalVariables._console
import com.m8test.script.GlobalVariables._threads
// 启动一个子线程,第一个参数为子线程名称,第二个参数为子线程需要的执行的代码,也就是一个定时任务,子线程启动时第一个执行的就是这个定时任务
val thread = _threads.start("test-sub-thread") {
// 输出日志
_console.log("1 子线程代码执行了", _threads.getCurrent())
// 如果这里执行的代码耗时短,并且你想在其他线程使用当前子线程的定时器,那么最好这里添加一个定时任务
val t = it.getTimer()
// 防止子线程直接结束从而导致其他线程通过定时器添加的任务无法执行, 这可以保证1秒钟内当前线程不会结束(子线程)
t.setTimeout({}, 1000)
}
// 获取子线程对应定时器
val timer = thread.getTimer()
// 添加定时任务到子线程, 两秒钟后执行
timer.setTimeout({
// 输出日志
_console.log("2 子线程定时任务执行了", _threads.getCurrent())
}, 2000)
// 到这里主线程的定时任务全部执行完成,但是还不会结束,他会等待所有子线程的所有定时任务执行完成
_console.log("脚本主线程日志", _threads.getCurrent())
// 启动一个子线程,第一个参数为子线程名称,第二个参数为子线程需要的执行的代码,也就是一个定时任务,子线程启动时第一个执行的就是这个定时任务
def thread = $threads.start("test-sub-thread") {
// 输出日志
$console.log("1 子线程代码执行了", $threads.getCurrent())
// 如果这里执行的代码耗时短,并且你想在其他线程使用当前子线程的定时器,那么最好这里添加一个定时任务
def t = it.getTimer()
// 防止子线程直接结束从而导致其他线程通过定时器添加的任务无法执行, 这可以保证1秒钟内当前线程不会结束(子线程)
t.setTimeout({}, 1000)
}
// 获取子线程对应定时器
def timer = thread.getTimer()
// 添加定时任务到子线程, 两秒钟后执行
timer.setTimeout({
// 输出日志
$console.log("2 子线程定时任务执行了", $threads.getCurrent())
}, 2000)
// 到这里主线程的定时任务全部执行完成,但是还不会结束,他会等待所有子线程的所有定时任务执行完成
$console.log("脚本主线程日志", $threads.getCurrent())
import com.m8test.script.core.api.thread.NewScriptThread;
import com.m8test.script.core.api.thread.Timer;
import kotlin.jvm.functions.Function1;
import static com.m8test.script.GlobalVariables.$console;
import static com.m8test.script.GlobalVariables.$threads;
// 启动一个子线程,第一个参数为子线程名称,第二个参数为子线程需要的执行的代码,也就是一个定时任务,子线程启动时第一个执行的就是这个定时任务
NewScriptThread thread = $threads.start("test-sub-thread", new Function1() {
@Override
public Object invoke(Object o) {
NewScriptThread it = (NewScriptThread) o;
// 输出日志
$console.log("1 子线程代码执行了", $threads.getCurrent());
// 如果这里执行的代码耗时短,并且你想在其他线程使用当前子线程的定时器,那么最好这里添加一个定时任务
Timer t = it.getTimer();
// 防止子线程直接结束从而导致其他线程通过定时器添加的任务无法执行, 这可以保证1秒钟内当前线程不会结束(子线程)
t.setTimeout(new Function1() {
@Override
public Object invoke(Object o) {
return null;
}
}, 1000);
return null;
}
});
// 获取子线程对应定时器
Timer timer = thread.getTimer();
// 添加定时任务到子线程, 两秒钟后执行
timer.setTimeout(new Function1() {
@Override
public Object invoke(Object o) {
// 输出日志
$console.log("2 子线程定时任务执行了", $threads.getCurrent());
return null;
}
}, 2000);
// 到这里主线程的定时任务全部执行完成,但是还不会结束,他会等待所有子线程的所有定时任务执行完成
$console.log("脚本主线程日志", $threads.getCurrent());
// 启动一个子线程,第一个参数为子线程名称,第二个参数为子线程需要的执行的代码,也就是一个定时任务,子线程启动时第一个执行的就是这个定时任务
let thread = $threads.start("test-sub-thread", function (it) {
// 输出日志
$console.log("1 子线程代码执行了", $threads.getCurrent())
// 如果这里执行的代码耗时短,并且你想在其他线程使用当前子线程的定时器,那么最好这里添加一个定时任务
let t = it.getTimer()
// 防止子线程直接结束从而导致其他线程通过定时器添加的任务无法执行, 这可以保证1秒钟内当前线程不会结束(子线程)
t.setTimeout({}, 1000)
})
// 获取子线程对应定时器
let timer = thread.getTimer()
// 添加定时任务到子线程, 两秒钟后执行
timer.setTimeout(function () {
// 输出日志
$console.log("2 子线程定时任务执行了", $threads.getCurrent())
}, 2000)
// 到这里主线程的定时任务全部执行完成,但是还不会结束,他会等待所有子线程的所有定时任务执行完成
$console.log("脚本主线程日志", $threads.getCurrent())
-- 启动一个子线程,第一个参数为子线程名称,第二个参数为子线程需要的执行的代码,也就是一个定时任务,子线程启动时第一个执行的就是这个定时任务
local thread = _threads:start("test-sub-thread", function(it)
-- 输出日志
_console:log("1 子线程代码执行了", _threads:getCurrent())
-- 如果这里执行的代码耗时短,并且你想在其他线程使用当前子线程的定时器,那么最好这里添加一个定时任务
local t = it:getTimer()
-- 防止子线程直接结束从而导致其他线程通过定时器添加的任务无法执行, 这可以保证1秒钟内当前线程不会结束(子线程)
t:setTimeout(function() end, 1000)
end)
-- 获取子线程对应定时器
local timer = thread:getTimer()
-- 添加定时任务到子线程, 两秒钟后执行
timer:setTimeout(function()
-- 输出日志
_console:log("2 子线程定时任务执行了", _threads:getCurrent())
end, 2000)
-- 到这里主线程的定时任务全部执行完成,但是还不会结束,他会等待所有子线程的所有定时任务执行完成
_console:log("脚本主线程日志", _threads:getCurrent())
<?php
// php脚本不允许创建子线程, 如果创建子线程可能会导致脚本直接崩溃
# python脚本不允许创建子线程, 如果创建子线程可能会导致脚本直接崩溃
# ruby脚本不允许创建子线程, 如果创建子线程可能会导致脚本直接崩溃

使用UI线程
你可以在脚本中将定时任务添加到 UI线程 中执行。有些操作(例如Toast)可能需要在Android UI线程中才能正确执行。 如果没有必要,最好不要使用UI线程执行定时任务,因为耗时过长的任务可能会导致界面卡顿。
import com.m8test.script.GlobalVariables._console
import com.m8test.script.GlobalVariables._threads
// 在 android ui线程中添加延时任务
_threads.getUI().getTimer().setTimeout({
_console.log("在android ui线程10秒钟后执行的代码")
}, 10000)
// 到这里主线程所有任务都执行完成了,但是需要等待android ui 线程的任务执行完成才会停止
_console.log("脚本主线程执行的代码")
// 在 android ui线程中添加延时任务
$threads.getUI().getTimer().setTimeout({
$console.log("在android ui线程10秒钟后执行的代码")
}, 10000)
// 到这里主线程所有任务都执行完成了,但是需要等待android ui 线程的任务执行完成才会停止
$console.log("脚本主线程执行的代码")
import kotlin.jvm.functions.Function1;
import static com.m8test.script.GlobalVariables.$console;
import static com.m8test.script.GlobalVariables.$threads;
// 在 android ui线程中添加延时任务
$threads.getUI().getTimer().setTimeout(new Function1() {
@Override
public Object invoke(Object o) {
$console.log("在android ui线程10秒钟后执行的代码");
return null;
}
}, 10000);
// 到这里主线程所有任务都执行完成了,但是需要等待android ui 线程的任务执行完成才会停止
$console.log("脚本主线程执行的代码");
// 在 android ui线程中添加延时任务
$threads.getUI().getTimer().setTimeout(function () {
$console.log("在android ui线程10秒钟后执行的代码")
}, 10000)
// 到这里主线程所有任务都执行完成了,但是需要等待android ui 线程的任务执行完成才会停止
$console.log("脚本主线程执行的代码")
-- 在 android ui线程中添加延时任务
_threads:getUI():getTimer():setTimeout(function()
_console:log("在android ui线程10秒钟后执行的代码")
end, 10000)
-- 到这里主线程所有任务都执行完成了,但是需要等待android ui 线程的任务执行完成才会停止
_console:log("脚本主线程执行的代码")
<?php
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
// 在 android ui线程中添加延时任务
$threads->getUI()->getTimer()->setTimeout(function ($p) use ($console) {
$console->log(javaString("在android ui线程10秒钟后执行的代码"));
}, 10000);
// 到这里主线程所有任务都执行完成了,但是需要等待android ui 线程的任务执行完成才会停止
$console->log(javaString("脚本主线程执行的代码"));
from m8test_java.com.m8test.script.GlobalVariables import _console
from m8test_java.com.m8test.script.GlobalVariables import _threads
# 在 android ui线程中添加延时任务
_threads.getUI().getTimer().setTimeout(lambda p: _console.log("在android ui线程10秒钟后执行的代码"), 10000)
# 到这里主线程所有任务都执行完成了,但是需要等待android ui 线程的任务执行完成才会停止
_console.log("脚本主线程执行的代码")
# encoding: utf-8
# 在 android ui线程中添加延时任务
$threads.getUI().getTimer().setTimeout(lambda { |p|
$console.log("在android ui线程10秒钟后执行的代码")
}, 10000)
# 到这里主线程所有任务都执行完成了,但是需要等待android ui 线程的任务执行完成才会停止
$console.log("脚本主线程执行的代码")
Last modified: 01 October 2025