概述
不同应用在各自独立的进程中运行。当应用以任何形式启动时,系统为其创建进程,该进程将持续运行。当进程完成当前任务处于等待状态,且系统资源不足时,系统自动回收。
在启动应用时,系统会为该应用创建一个称为“主线程”的执行线程。该线程随着应用创建或消失,是应用的核心线程。UI界面的显示和更新等操作,都是在主线程上进行。主线程又称UI线程,默认情况下,所有的操作都是在主线程上执行。如果需要执行比较耗时的任务(如下载文件、查询数据库),可创建其他线程来处理。
如果应用的业务逻辑比较复杂,可能需要创建多个线程来执行多个任务。这种情况下,代码复杂难以维护,任务与线程的交互也会更加繁杂。要解决此问题,开发者可以使用TaskDispatcher来分发不同的任务。
TaskDispatcher介绍
TaskDispatcher是一个任务分发器,它是Ability分发任务的基本接口,隐藏任务所在线程的实现细节。
为保证应用有更好的响应性,我们需要设计任务的优先级。在UI线程上运行的任务默认以高优先级运行,如果某个任务无需等待结果,则可以用低优先级。
线程优先级介绍:
HIGH:最高任务优先级,比默认优先级、低优先级的任务有更高的几率得到执行。
DEFAULT:默认任务优先级, 比低优先级的任务有更高的几率得到执行。
LOW:低任务优先级,比高优先级、默认优先级的任务有更低的几率得到执行。
TaskDispatcher具有多种实现,每种实现对应不同的任务分发器。在分发任务时可以指定任务的优先级,由同一个任务分发器分发出的任务具有相同的优先级。系统提供的任务分发器有GlobalTaskDispatcher、ParallelTaskDispatcher、SerialTaskDispatcher 、SpecTaskDispatcher。
实践
1.同步派发任务syncDispatch
发任务并在当前线程等待任务执行完成。在返回前,当前线程会被阻塞
- /**
- * 同步派发任务
- */
- private void syncDispatch() {
- TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
- globalTaskDispatcher.syncDispatch(new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "sync task1 run");
- }
- });
- HiLog.info(LABEL_LOG, "after sync task1");
- globalTaskDispatcher.syncDispatch(new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "sync task2 run");
- }
- });
- HiLog.info(LABEL_LOG, "after sync task2");
- globalTaskDispatcher.syncDispatch(new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "sync task3 run");
- }
- });
- HiLog.info(LABEL_LOG, "after sync task3");
- }
运行之后查看日志:
从运行结果我们可以看到,只有在当前线程等待任务执行完成之后才会继续往下执行,否则当前线程会被阻塞,所以在使用syncDispatch的时候我们需要注意,如果对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)进行同步派发任务。在特定的线程执行顺序下将导致死锁。
2.异步派发任务asyncDispatch
派发任务,并立即返回,返回值是一个可用于取消任务的接口。
- /**
- * 异步派发任务
- */
- private void asyncDispatch() {
- TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
- Revocable revocable = globalTaskDispatcher.asyncDispatch(new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "async task1 run");
- }
- });
- HiLog.info(LABEL_LOG, "after async task1");
- }
运行之后查看日志:
从运行结果我们可以看到,只有在当前线程等待任务执行完成之后才会继续往下执行,否则当前线程会被阻塞,所以在使用
3. 异步延迟派发任务delayDispatch
异步执行,函数立即返回,内部会在延时指定时间后将任务派发到相应队列中。延时时间参数仅代表在这段时间以后任务分发器会将任务加入到队列中,任务的实际执行时间可能晚于这个时间。具体比这个数值晚多久,取决于队列及内部线程池的繁忙情况。
- /**
- * 异步延迟派发任务
- */
- private void delayDispatch() {
- final long callTime = System.currentTimeMillis();
- final long delayTime = 50L;
- TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
- Revocable revocable = globalTaskDispatcher.delayDispatch(new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "delayDispatch task1 run");
- final long actualDelay = System.currentTimeMillis() - callTime;
- HiLog.info(LABEL_LOG, "actualDelayTime >= delayTime: %{public}b", (actualDelay >= delayTime));
- }
- }, delayTime);
- HiLog.info(LABEL_LOG, "after delayDispatch task1");
- }
运行之后查看日志:
从运行结果我们可以看出,程序首先执行"after delayDispatch task1",然后执行"delayDispatch task1 run",最后执行"actualDelayTime >= delayTime: %{public}b", (actualDelay >= delayTime),这里 actualDelayTime >= delayTime: true可以看出延时时间参数仅代表在这段时间以后任务分发器会将任务加入到队列中,任务的实际执行时间可能晚于这个时间。
4. 任务组Group
表示一组任务,且该组任务之间有一定的联系,由TaskDispatcher执行createDispatchGroup创建并返回。将任务加入任务组,返回一个用于取消任务的接口。
- /**
- * 任务组
- */
- private void dispatchGroup() {
- String dispatcherName = "parallelTaskDispatcher";
- TaskDispatcher dispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
- // 创建任务组。
- Group group = dispatcher.createDispatchGroup();
- // 将任务1加入任务组,返回一个用于取消任务的接口。
- dispatcher.asyncGroupDispatch(group, new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "download task1 is running");
- }
- });
- // 将与任务1相关联的任务2加入任务组。
- dispatcher.asyncGroupDispatch(group, new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "download task2 is running");
- }
- });
- // 在任务组中的所有任务执行完成后执行指定任务。
- dispatcher.groupDispatchNotify(group, new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "the close task is running after all tasks in the group are completed");
- }
- });
- }
运行之后查看日志:
5. 同步设置屏障任务syncDispatchBarrier
在任务组上设立任务执行屏障,同步等待任务组中的所有任务执行完成,再执行指定任务。
- /**
- * 同步设置屏障任务
- */
- private void syncDispatchBarrier() {
- String dispatcherName = "parallelTaskDispatcher";
- TaskDispatcher dispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
- // 创建任务组。
- Group group = dispatcher.createDispatchGroup();
- // 将任务加入任务组,返回一个用于取消任务的接口。
- dispatcher.asyncGroupDispatch(group, new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "task1 is running"); // 1
- }
- });
- dispatcher.asyncGroupDispatch(group, new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "task2 is running"); // 2
- }
- });
- dispatcher.syncDispatchBarrier(new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "barrier"); // 3
- }
- });
- HiLog.info(LABEL_LOG, "after syncDispatchBarrier"); // 4
- }
运行之后查看日志:
6. 异步设置屏障任务asyncDispatchBarrier
在任务组上设立任务执行屏障后直接返回,指定任务将在任务组中的所有任务执行完成后再执行。
- /**
- * 异步设置屏障任务
- */
- private void asyncDispatchBarrier() {
- TaskDispatcher dispatcher = createParallelTaskDispatcher("dispatcherName", TaskPriority.DEFAULT);
- // 创建任务组。
- Group group = dispatcher.createDispatchGroup();
- // 将任务加入任务组,返回一个用于取消任务的接口。
- dispatcher.asyncGroupDispatch(group, new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "task1 is running"); // 1
- }
- });
- dispatcher.asyncGroupDispatch(group, new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "task2 is running"); // 2
- }
- });
- dispatcher.asyncDispatchBarrier(new Runnable() {
- @Override
- public void run() {
- HiLog.info(LABEL_LOG, "barrier"); // 3
- }
- });
- HiLog.info(LABEL_LOG, "after asyncDispatchBarrier"); // 4
- }
运行之后查看日志:
总结
线程它就像一面双刃剑,用的好的时候可以给我们带来事半功倍等效果,用的不好时就会给我们带来困扰,并且这个困扰还不是一时半会能解决掉的(因为发现问题的时候,往往是到了需要优化期了,各项业务相互牵扯),故在项目初期就需要严格考虑考量这些问题了。