鸿蒙 任务分发讲解
鸿蒙开发核心之任务分发讲解
一、简单说明
说起鸿蒙的线程就不得不说Android的线程,相信都知道在Android中,每一个应用都会有自己的主线程和其他的子线程,主线程负责处理大部分业务,负责UI的显示和更新等操作,所以又称之为UI线程,同时,我们还知道不能够在UI线程中进行一些耗时操作,比如网络访问、数据读写等一些功能会启动一个子线程,或者异步线程来进行,这样做可以避免出现程序无法响应这样的情况,也就是ANR(Android Not Response)。当然这是造成程序ANR的原因之一,还有一些其他原因,比如系统逻辑,空指针,内存溢出。那么我说了这么多关于Android中线程使用的说明是为了什么呢?当然是抛砖引玉,就是说明鸿蒙的线程和Android的线程是异父异母的亲兄弟啊!所以刚才我说的以上用法在鸿蒙中同样可行,这个解释你满不满意呢?(PS:不满意也不用动手,都是文明人,键盘不饶人。)
二、任务分发器
如果刚才的内容你都知道的话,那么下面的内容你就不一定也知道,如果你知道,就当我没说。下面我们从实际的场景来着手说明,例如自动更新:应用在进入首页时需要通过访问后台得到最新的应用版本,同时将版本信息保存到本地,当版本不一致时可以选择下载安装更新,但是更新又是在后台进行的,因为你不能够让用户等你更新完再使用。这样一个流程要在Android里面实现也不是什么难事,同一时间执行多个任务且不在主线程执行,也许我这个比方打的不是很好,你应该能懂我的意思。因为这个业务还比较的简单,举这个例子就是为了说明TaskDispatcher (音译:他死 diss 怕球儿~),注意这个儿化音,划重点,要考的。后面三个字说的要快,并且带点伦敦郊区的口音,只要你的英语说的够快,别人就不知道你说的是中文。天下武功,唯快不破。
TaskDispatcher 是一个任务分发器,它是 Ability 分发任务的基本接口,隐藏任务所在线程的实现细节。为保证应用有更好的响应性,我们需要设计任务的优先级。在 UI 线程上运行的任务默认以高优先级运行,如果某个任务无需等待结果,则可以用低优先级。级别如下
HIGH 最高任务优先级,比默认优先级、低优先级的任务有更高的几率得到执行。
DEFAULT 默认任务优先级, 比低优先级的任务有更高的几率得到执行。
LOW 低任务优先级,比高优先级、默认优先级的任务有更低的几率得到执行。
TaskDispatcher 具有多种实现,每种实现对应不同的任务分发器。在分发任务时可以指定任务的优先级,由同一个任务分发器分发出的任务具有相同的优先级。系统提供的任务分发器有 GlobalTaskDispatcher、ParallelTaskDispatcher、SerialTaskDispatcher 、SpecTaskDispatcher。为了方便演示代码我新建一个手表的鸿蒙项目,如下图所示:
① GlobalTaskDispatcher
全局并发任务分发器,(音译:狗萝卜 他死 diss 怕球儿~,解说:前面三个字要读的比后面三个字更快才行,有难度的)由 Ability 执行 getGlobalTaskDispatcher()获取。适用于任务之间没有联系的情况。一个应用只有一个 GlobalTaskDispatcher,它在程序结束时才被销毁。
/**
* priority 优先级
**/
public TaskDispatcher getGlobalTaskDispatcher(TaskPriority priority) {
throw new RuntimeException("Stub!");
}
我在MainAbility中通过调用getGlobalTaskDispatcher()方法,传入一个等级TaskPriority.DEFAULT,这个也可以用null来代替。然后得到一个TaskDispatcher的对象
② ParallelTaskDispatcher
并发任务分发器,(音译:佩尔楼 他死 diss 怕球儿~)由 Ability 执行 createParallelTaskDispatcher()创建并返回。与GlobalTaskDispatcher 不同的是,ParallelTaskDispatcher 不具有全局唯一性,可以创建多个。开发者在创建或销毁 dispatcher 时,需要持有对应的对象引用 。
/**
* 创建并发任务分发器
* @param name 名称
* @param priority 优先级
* @return parallelTaskDispatcher
*/
public TaskDispatcher createParallelTaskDispatcher(String name, TaskPriority priority) {
throw new RuntimeException("Stub!");
}
创建时传两个参数,一个字符串的名字,一个是等级优先级,这里我用null也可以的。这里你又可以想一下为什么需要多一个名字参数,当然是为了区别不同的并发任务分布器,不过至于可不可以重名我就不知道了,理论上来说,应该不允许重名,说不定会给你报错,可以自己去尝试。
③ SerialTaskDispatcher
串行任务分发器(音译:C瑞尔 他死 diss 怕球儿~)由 Ability 执行 createSerialTaskDispatcher()创建并返回。由该分发器分发的所有的任务都是按顺序执行,但是执行这些任务的线程并不是固定的。如果要执行并行任务,应使用 ParallelTaskDispatcher 或者 GlobalTaskDispatcher,而不是创建多个SerialTaskDispatcher。如果任务之间没有依赖,应使用 GlobalTaskDispatcher 来实现。它的创建和销毁由开发者自己管理,开发者在使用期间需要持有该对象引用。你可以把它理解成一个有序列表,但是列表里面的值是可变的。它的方法如下:
/**
* 创建串行任务分发器
* @param name 名字
* @param priority 优先级
* @return SerialTaskDispatcher
*/
public TaskDispatcher createSerialTaskDispatcher(String name, TaskPriority priority) {
throw new RuntimeException("Stub!");
}
获取方法
④ SpecTaskDispatcher
专有任务分发器(音译:思拜客 他死 diss 怕球儿~) 专有任务分发器,绑定到专有线程上的任务分发器。目前已有的专有线程是主线程。
UITaskDispatcher 和 MainTaskDispatcher 都属于 SpecTaskDispatcher。建议使用UITaskDispatcher。
UITaskDispatcher:绑定到应用主线程的专有任务分发器, 由 Ability 执行getUITaskDispatcher()创建并返回。 由该分发器分发的所有的任务都是在主线程上按顺序执行,它在应用程序结束时被销毁。之前我们说主线程上不能执行过多的任务,那么这个分发器就可以绑定到主线程上,进而可以执行很多任务,你可以这么理解。方法如下:
/**
* 获取主线程任务分发器
* @return MainTaskDispatcher
*/
public final TaskDispatcher getMainTaskDispatcher() {
throw new RuntimeException("Stub!");
}
/**
* 获取UI线程任务分发器
* @return UITaskDispatcher
*/
public final TaskDispatcher getUITaskDispatcher() {
throw new RuntimeException("Stub!");
}
⑤ 同步派发任务(syncDispatch)
就是一个同步进行的线程,下面由globalTaskDispatcher来派发同步任务。
修改MainAbility。
//日志
private static final HiLogLabel label = new HiLogLabel(3, 0xD001100, "ThreadDemo");
private TaskDispatcher globalTaskDispatcher;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(MainAbilitySlice.class.getName());
//获取全局并发任务分发器 一个应用程序只有一个
globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
//GlobalTaskDispatcher 派发同步任务
syncTask();
}
syncTask方法中中派发三个同步任务,打印日志
/**
* GlobalTaskDispatcher 派发同步任务
*/
private void syncTask() {
globalTaskDispatcher.syncDispatch(new Runnable() {
@Override
public void run() {
HiLog.info(label, "sync task1 run");
}
});
HiLog.info(label, "after sync task1");
globalTaskDispatcher.syncDispatch(new Runnable() {
@Override
public void run() {
HiLog.info(label,"sync task2 run");
}
});
HiLog.info(label,"after sync task2");
globalTaskDispatcher.syncDispatch(new Runnable() {
@Override
public void run() {
HiLog.info(label,"sync task3 run");
}
});
HiLog.info(label,"after sync task3");
}
运行之后,日志如下所示:
虽然现在是没有问题,但是如果对 syncDispatch 使用不当, 将会导致死锁。如下情形可能导致死锁发生:
- 在专有线程上,利用该专有任务分发器进行 syncDispatch。
- 在被某个串行任务分发器(dispatcher_a)派发的任务中,再次利用同一个串行任务分发器(dispatcher_a)对象派发任务。
- 在被某个串行任务分发器(dispatcher_a)派发的任务中,经过数次派发任务,最终又利用该(dispatcher_a)串行任务分发器派发任务。例如:dispatcher_a 派发的任务使用 dispatcher_b 进行任务的派发,在 dispatcher_b 派发的任务中又利用 dispatcher_a 进行派发任务。
- 串行任务分发器(dispatcher_a)派发的任务中利用串行任务分发器(dispatcher_b)进行同步派发任务,同时dispatcher_b 派发的任务中利用串行任务分发器(dispatcher_a)进行同步派发任务。在特定的线程执行顺序下将导致死锁。
⑥ 异步派发任务(asyncDispatch)
新写一个asyncTask方法,里面的内容和syncTask差不多。
/**
* GlobalTaskDispatcher 派发同步任务
*/
private void asyncTask() {
globalTaskDispatcher.asyncDispatch(new Runnable() {
@Override
public void run() {
HiLog.info(label,"sync task1 run");
}
});
HiLog.info(label,"after sync task1");
}
然后在onStart中调用即可然后再运行。同时这个异步派发是任务是可以取消的。Revocable,globalTaskDispatcher.asyncDispatch的返回值就是Revocable,那么上面async中的代码就可以这样写。如下所示:
/**
* GlobalTaskDispatcher 派发同步任务
*/
private void asyncTask() {
Revocable revocable = globalTaskDispatcher.asyncDispatch(new Runnable() {
@Override
public void run() {
HiLog.info(label, "async task1 run");
}
});
//撤销任务分发
revocable.revoke();
HiLog.info(label, "after async task1");
}
在运行一下:
很明显里面的任务已经撤销了、
⑦ 异步延迟派发任务(delayDispatch)
异步延迟派发任务:异步执行,函数立即返回,内部会在延时指定时间后将任务派发到相应队列中。延时时间参数仅代表在这段时间以后任务分发器会将任务加入到队列中,任务的实际执行时间可能晚于这个时间。具体比这个数值晚多久,取决于队列及内部线程池的繁忙情况。下面用代码演示一下:
/**
* 当前时间
*/
final long currentTime = System.currentTimeMillis();
/**
* 延迟时间 100ms
*/
final long delayTime = 100;
delayTask方法如下:
/**
* GlobalTaskDispatcher 派发异步延时任务
*/
private void delayTask() {
globalTaskDispatcher.delayDispatch(new Runnable() {
@Override
public void run() {
HiLog.info(label, "delay task1 run");
final long actualDelayMs = System.currentTimeMillis() - currentTime;
HiLog.info(label,"actualDelayTime >= delayTime : " + (actualDelayMs >= delayTime));
}
},delayTime);
HiLog.info(label, "after delay task1");
}
日志如下:
⑧ 任务组(Group)
表示一组任务,且该组任务之间有一定的联系,由 TaskDispatcher 执行createDispatchGroup 创建并返回,代码如下所示。
/**
* 任务组
*/
private void taskGroup(Context context) {
//创建并发任务分发器
TaskDispatcher dispatcher = context.createParallelTaskDispatcher("parallelTaskDispatcher", TaskPriority.DEFAULT);
//创建任务组
Group group = dispatcher.createDispatchGroup();
// 将任务 1 加入任务组,
dispatcher.asyncGroupDispatch(group,new Runnable() {
@Override
public void run() {
HiLog.info(label,"download task1 is running");
}
});
// 将任务 2 加入任务组,
dispatcher.asyncGroupDispatch(group, new Runnable() {
@Override
public void run() {
HiLog.info(label,"download task2 is running");
}
});
// 在任务组中的所有任务执行完成后执行指定任务。
dispatcher.groupDispatchNotify(group, new Runnable() {
@Override
public void run() {
HiLog.info(label, "the close task is running after all tasks in the group are completed");
}
});
}
onStart方法中调用运行效果如下,我运行了两次,两次日志是不一样的,如果里面执行的业务是不一样的,则耗时长的后完成。
⑨ 取消任务(Revocable)
Revocable 是取消一个异步任务的接口。异步任务包括通过 asyncDispatch、delayDispatch、asyncGroupDispatch 派发的任务。如果任务已经在执行中或执行完成,则会返回取消失败。
/**
* 取消异步任务
*/
private void revocableAsyncTask(Context context) {
TaskDispatcher dispatcher = context.getUITaskDispatcher();
Revocable revocable = dispatcher.delayDispatch(new Runnable() {
@Override
public void run() {
HiLog.info(label, "delay dispatch");
}
}, 10);
boolean revoked = revocable.revoke();
HiLog.info(label, "" + revoked);
}
在onStart()中调用
//取消异步任务
revocableAsyncTask(MainAbility.this);
运行效果如下:⑩ 同步设置屏障任务 (syncDispatchBarrier)
在任务组上设立任务执行屏障,同步等待任务组中的所有任务执行完成,再执行指定任务。在全局并发任务分发器(GlobalTaskDispatcher)上同步设置任务屏障,将不会起到屏障作用。就比如说考试提前交卷,A提前20分钟,B提前10分钟,但你只是离开了考室,离不开考场,这个考场的门就是这个屏障,无论你是提前多久,你都要等到所有考生考完,老师收卷之后才能离开考场,就是这么一个理。代码如下:
/**
* 同步设置屏障任务
*/
private void setSyncDispatchBarrier(Context context) {
TaskDispatcher dispatcher = context.createParallelTaskDispatcher("parallelTask",TaskPriority.DEFAULT);
//创建任务组
Group group = dispatcher.createDispatchGroup();
//将任务1加入任务组
dispatcher.asyncGroupDispatch(group, new Runnable() {
@Override
public void run() {
HiLog.info(label,"task1 is running");
}
});
//将任务2加入任务组
dispatcher.asyncGroupDispatch(group, new Runnable() {
@Override
public void run() {
HiLog.info(label,"task2 is running");
}
});
dispatcher.asyncDispatchBarrier(new Runnable() {
@Override
public void run() {
HiLog.info(label,"Barrier");
}
});
HiLog.info(label,"after syncDispatchBarrier");
}
在onStart()中调用,
//同步设置屏障任务
setSyncDispatchBarrier(MainAbility.this);
运行⑪ 异步设置屏障任务 (asyncDispatchBarrier)
在任务组上设立任务执行屏障后直接返回,指定任务将在任务组中的所有任务执行完成后再执行。在全局并发任务分发器(GlobalTaskDispatcher)上异步设置任务屏障,将不会起到屏障作用。可以使用并发任务分发器(ParallelTaskDispatcher)分离不同的任务组,达到微观并行、宏观串行的行为。
/**
* 异步设置屏障任务
*/
private void setAsyncDispatchBarrier(Context context) {
TaskDispatcher dispatcher = context.createParallelTaskDispatcher("AsyncParallelTaskDispatcher", TaskPriority.DEFAULT);
// 创建任务组
Group group = dispatcher.createDispatchGroup();
//将任务1加入任务组
dispatcher.asyncGroupDispatch(group, new Runnable() {
@Override
public void run() {
HiLog.info(label, "task1 is running"); // 1
}
});
//将任务2加入任务组
dispatcher.asyncGroupDispatch(group, new Runnable() {
@Override
public void run() {
HiLog.info(label, "task2 is running"); // 2
}
});
dispatcher.asyncDispatchBarrier(new Runnable() {
@Override
public void run() {
HiLog.info(label, "barrier"); // 3
}
});
HiLog.info(label, "after syncDispatchBarrier"); // 4
}
在onStart()中调用
//异步设置屏障任务
setAsyncDispatchBarrier(MainAbility.this);
运行效果
⑫ 执行多次任务(applyDispatch)
对指定任务执行多次
/**
* 执行多次任务
*/
private void multipleTasks() {
//总执行次数
final int total = 10;
//倒数计时器
final CountDownLatch latch = new CountDownLatch(total);
//长整形列表数组
final ArrayList<Long> indexList = new ArrayList<>(total);
TaskDispatcher dispatcher = createParallelTaskDispatcher("dispatcher",
TaskPriority.DEFAULT);
//执行多次任务
dispatcher.applyDispatch((index) -> {
indexList.add(index);
latch.countDown();
HiLog.info(label, "long: "+index);
}, total);
//设置任务超时
try {
latch.await();
} catch (InterruptedException e) {
HiLog.info(label, "latch exception");
}
//返回true则说明执行了10次
HiLog.info(label, "list size matches," + (total == indexList.size()));
}
运行效果如下:
下一篇,我们讲讲线程通信