暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

鸿蒙的线程管理,看完浑身通透!

鸿蒙技术社区 2021-09-16
640

不同应用在各自独立的进程中运行。当应用以任何形式启动时,系统为其创建进程,该进程将持续运行。当进程完成当前任务处于等待状态,且系统资源不足时,系统自动回收。


在启动应用时,系统会为该应用创建一个称为“主线程”的执行线程。该线程随着应用创建或消失,是应用的核心线程。UI 界面的显示和更新等操作,都是在主线程上进行。


主线程又称 UI 线程,默认情况下,所有的操作都是在主线程上执行。如果需要执行比较耗时的任务(如下载文件、查询数据库),可创建其他线程来处理。


如果应用的业务逻辑比较复杂,可能需要创建多个线程来执行多个任务。这种情况下,代码复杂难以维护,任务与线程的交互也会更加繁杂。


要解决此问题,开发者可以使用 TaskDispatcher 来分发不同的任务。


TaskDispatcher 介绍


TaskDispatcher 是一个任务分发器,它是 Ability 分发任务的基本接口,隐藏任务所在线程的实现细节。


为保证应用有更好的响应性,我们需要设计任务的优先级。在 UI 线程上运行的任务默认以高优先级运行,如果某个任务无需等待结果,则可以用低优先级。


线程优先级介绍:

  • HIGH:最高任务优先级,比默认优先级、低优先级的任务有更高的几率得到执行。

  • DEFAULT:默认任务优先级, 比低优先级的任务有更高的几率得到执行。

  • LOW:低任务优先级,比高优先级、默认优先级的任务有更低的几率得到执行。


TaskDispatcher 具有多种实现,每种实现对应不同的任务分发器。在分发任务时可以指定任务的优先级,由同一个任务分发器分发出的任务具有相同的优先级。


系统提供的任务分发器有:

  • GlobalTaskDispatcher

  • ParallelTaskDispatcher

  • SerialTaskDispatcher 

  • SpecTaskDispatcher


实践


①同步派发任务 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)进行同步派发任务。在特定的线程执行顺序下将导致死锁。


②异步派发任务 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");
    }


运行之后查看日志:

从运行结果我们可以看到,只有在当前线程等待任务执行完成之后才会继续往下执行,否则当前线程会被阻塞,所以在使用。


③异步延迟派发任务 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 可以看出延时时间参数仅代表在这段时间以后任务分发器会将任务加入到队列中,任务的实际执行时间可能晚于这个时间。


④任务组 Group


表示一组任务,且该组任务之间有一定的联系,由 TaskDispatcher 执行 createDispatchGroup 创建并返回。


将任务加入任务组,返回一个用于取消任务的接口。

     /**
     * 任务组
     */

    private void dispatchGroup({
        String dispatcherName = "parallelTaskDispatcher";
        TaskDispatcher dispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
        // 创建任务组。
        Group group = dispatcher.createDispatchGroup();
        // 将任务1加入任务组,返回一个用于取消任务的接口。
        dispatcher.asyncGroupDispatch(groupnew Runnable() {
            @Override
            public void run(
{
                HiLog.info(LABEL_LOG, "download task1 is running");
            }
        });
        // 将与任务1相关联的任务2加入任务组。
        dispatcher.asyncGroupDispatch(groupnew Runnable() {
            @Override
            public void run(
{
                HiLog.info(LABEL_LOG, "download task2 is running");
            }
        });
        // 在任务组中的所有任务执行完成后执行指定任务。
        dispatcher.groupDispatchNotify(groupnew Runnable() {
            @Override
            public void run(
{
                HiLog.info(LABEL_LOG, "the close task is running after all tasks in the group are completed");
            }
        });
    }


运行之后查看日志:

⑤同步设置屏障任务 syncDispatchBarrier


在任务组上设立任务执行屏障,同步等待任务组中的所有任务执行完成,再执行指定任务。

        /**
     * 同步设置屏障任务
     */

    private void syncDispatchBarrier({
        String dispatcherName = "parallelTaskDispatcher";
        TaskDispatcher dispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
        // 创建任务组。
        Group group = dispatcher.createDispatchGroup();
        // 将任务加入任务组,返回一个用于取消任务的接口。
        dispatcher.asyncGroupDispatch(groupnew Runnable() {
            @Override
            public void run(
{
                HiLog.info(LABEL_LOG, "task1 is running");  // 1
            }
        });
        dispatcher.asyncGroupDispatch(groupnew 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
    }


运行之后查看日志:

⑥异步设置屏障任务 asyncDispatchBarrier


在任务组上设立任务执行屏障后直接返回,指定任务将在任务组中的所有任务执行完成后再执行。

   /**
     * 异步设置屏障任务
     */

    private void asyncDispatchBarrier({
        TaskDispatcher dispatcher = createParallelTaskDispatcher("dispatcherName", TaskPriority.DEFAULT);
        // 创建任务组。
        Group group = dispatcher.createDispatchGroup();
        // 将任务加入任务组,返回一个用于取消任务的接口。
        dispatcher.asyncGroupDispatch(groupnew Runnable() {
            @Override
            public void run(
{
                HiLog.info(LABEL_LOG, "task1 is running");  // 1
            }
        });
        dispatcher.asyncGroupDispatch(groupnew 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
    }


运行之后查看日志:

总结


线程它就像一面双刃剑,用的好的时候可以给我们带来事半功倍等效果,用的不好时就会给我们带来困扰。


并且这个困扰还不是一时半会能解决掉的(因为发现问题的时候,往往是到了需要优化期了,各项业务相互牵扯),故在项目初期就需要严格考虑考量这些问题了。


作者:汪刚
预约今晚的鸿蒙直播课

👇

一周年庆 抽奖活动

关注鸿蒙技术社区订阅号,转发此图文至朋友圈,公众号回复转发的截图,抽鸿蒙新款 matepad11价值 399、299 元的鸿蒙盲盒和 2000ml 健康随身杯


回复“周年庆”抽奖

👇


“阅读原文”了解更多

文章转载自鸿蒙技术社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论