DEV Community

SameX
SameX

Posted on

HarmonyOS Case Practice: Multithreading Task Scheduling and Performance Optimization in an Image Processing Application

This article aims to deeply explore the technical details of the Huawei HarmonyOS Next system (up to API 12 as of now), and is summarized based on actual development practices.
It mainly serves as a vehicle for technical sharing and communication. Mistakes and omissions are inevitable. Colleagues are welcome to put forward valuable opinions and questions so that we can make progress together.
This article is original content, and any form of reprint must indicate the source and the original author.

1. Project Requirement and Goal Analysis

Background:
Image processing is a typical CPU-intensive task. Especially when dealing with high-resolution images or requiring complex calculations such as image filtering and transformation, the performance of image processing will significantly affect the response speed of the application. In this case, through multithreading concurrent processing, the CPU utilization can be effectively improved, thereby optimizing the system performance.
Requirements:

  • Design an image processing application that needs to process images, including color adjustment, image filtering, etc.
  • The image processing tasks should support concurrent execution to fully utilize the computing power of multi-core CPUs.
  • Ensure data security in multithreading concurrency and prevent race conditions and data inconsistency.
  • Support the setting of different task priorities and optimize the task scheduling strategy.
  • Monitor the execution of tasks in real time and optimize for performance bottlenecks. Functional Requirements:
  • Concurrent processing of image data.
  • Support slice processing of large images.

- Provide performance monitoring and optimization tools.

2. Use of TaskGroup and Multitask Scheduling

TaskGroup Overview:
In ArkTS, TaskGroup is an advanced API for managing and scheduling multiple tasks. Through TaskGroup, we can execute image processing tasks in slices. Each task runs in an independent thread, and the task group can be scheduled concurrently to improve the efficiency of image processing.

Example of Image Slicing and Task Scheduling

To improve the efficiency of image processing, the image data can be sliced, and each slice is processed independently. Through TaskGroup, we can start multiple tasks to process these slices in parallel.

import { taskpool } from '@kit.ArkTS';
// Simulate an image processing function that processes a slice of an image
@Concurrent
function processImageSlice(slice: ArrayBuffer): ArrayBuffer {
    console.log('Processing image slice...');
    // Simulate image processing operations (such as filtering, color adjustment)
    return slice;
}
// Use TaskGroup to manage multiple image slice tasks
function processFullImage(image: ArrayBuffer): void {
    const sliceSize = image.byteLength / 3;
    const slice1 = image.slice(0, sliceSize);
    const slice2 = image.slice(sliceSize, sliceSize * 2);
    const slice3 = image.slice(sliceSize * 2);
    let group = new taskpool.TaskGroup();
    group.addTask(processImageSlice, slice1);
    group.addTask(processImageSlice, slice2);
    group.addTask(processImageSlice, slice3);
    // Execute the task group and handle the results
    taskpool.execute(group).then(results => {
        console.log('Image processing completed: ', results);
    }).catch(error => {
        console.error('Image processing task failed: ', error);
    });
}
Enter fullscreen mode Exit fullscreen mode

In this example, the image is divided into three parts and processed concurrently through TaskGroup. Each image slice task runs in an independent thread, and the final result is returned in a Promise way.

3. Performance Optimization of CPU-Intensive Tasks

Characteristics of CPU-Intensive Tasks:
In CPU-intensive tasks such as image processing, the main problem is how to effectively utilize CPU resources, especially the computing power of multi-core CPUs. Through multithread parallel computing, the image processing tasks can be distributed to multiple cores for execution, thereby improving the computing efficiency.
Optimization Strategies:

  • Task Slicing: By dividing a large image into multiple small slices and processing them separately, the processing time can be significantly reduced.
  • Task Scheduling: Through TaskPool to manage task execution, avoid time-consuming operations in the main thread, thereby improving the application response speed.
  • Avoid Thread Contention: During the processing, if multiple threads share the same resource, it is easy to cause contention and performance bottlenecks. We can avoid race conditions through data slicing or asynchronous locks. #### Example of CPU-Intensive Task
@Concurrent
function intensiveImageProcessing(slice: ArrayBuffer): ArrayBuffer {
    // Perform complex image processing, such as filtering, edge detection, etc.
    console.log('Performing CPU-intensive image processing...');
    return slice; // Return the processed image slice
}
function optimizeImageProcessing(image: ArrayBuffer): void {
    const sliceSize = image.byteLength / 4;
    const slices = [
        image.slice(0, sliceSize),
        image.slice(sliceSize, sliceSize * 2),
        image.slice(sliceSize * 2, sliceSize * 3),
        image.slice(sliceSize * 3),
    ];
    slices.forEach(slice => {
        let task: taskpool.Task = new taskpool.Task(intensiveImageProcessing, slice);
        taskpool.execute(task).then(result => {
            console.log('Slice processing completed: ', result);
        }).catch(error => {
            console.error('Slice processing failed: ', error);
        });
    });
}
Enter fullscreen mode Exit fullscreen mode

4. Task Priority and Task Distribution Strategy

In multitask scheduling, the setting of task priorities can affect the overall performance of the system. Tasks with higher priorities will be executed first to ensure that critical tasks can respond in a timely manner. For an image processing application, key processing steps can be set as high-priority tasks, and other non-critical tasks can be set as low-priority.
Task Priority Setting:
ArkTS provides task priority setting, which can be specified by taskpool.Priority.

Example of Task Priority

function processImageWithPriority(image: ArrayBuffer): void {
    const slice = image.slice(0, image.byteLength / 2);
    // Set a high-priority task
    let highPriorityTask: taskpool.Task = new taskpool.Task(intensiveImageProcessing, slice);
    taskpool.execute(highPriorityTask, taskpool.Priority.HIGH).then(result => {
        console.log('High-priority task completed: ', result);
    });
    // Set a low-priority task
    let lowPriorityTask: taskpool.Task = new taskpool.Task(intensiveImageProcessing, slice);
    taskpool.execute(lowPriorityTask, taskpool.Priority.LOW).then(result => {
        console.log('Low-priority task completed: ', result);
    });
}
Enter fullscreen mode Exit fullscreen mode

Through priority setting, the system can rationally schedule task resources to ensure the priority execution of critical tasks, thereby optimizing the user experience.

5. Performance Monitoring and Optimization

Performance Monitoring Tools:
During the image processing, monitoring the task execution is the key to optimizing performance. We can record the execution time, results, and problems of tasks through logs, analyze performance bottlenecks and optimize.
Optimization Strategies:

  • Slice Size Adjustment: Adjust the slice size of the image to balance the task granularity and system load.
  • Task Priority Optimization: Reasonably set the task priorities to ensure the rational allocation of system resources.
  • Multithreading Performance Monitoring: Record the processing time of each thread and analyze the performance bottlenecks during concurrent execution. #### Example of Performance Monitoring
@Concurrent
async function timedProcess(slice: ArrayBuffer): Promise<ArrayBuffer> {
    const start = Date.now();
    const result = intensiveImageProcessing(slice);
    const duration = Date.now() - start;
    console.log(`Task execution time: ${duration} milliseconds`);
    return result;
}
function processImageWithMonitoring(image: ArrayBuffer): void {
    const sliceSize = image.byteLength / 4;
    const slices = [
        image.slice(0, sliceSize),
        image.slice(sliceSize, sliceSize * 2),
        image.slice(sliceSize * 2, sliceSize * 3),
        image.slice(sliceSize * 3),
    ];
    slices.forEach(slice => {
        let task: taskpool.Task = new taskpool.Task(timedProcess, slice);
        taskpool.execute(task).then(result => {
            console.log('Slice processing completed: ', result);
        }).catch(error => {
            console.error('Processing failed: ', error);
        });
    });
}
Enter fullscreen mode Exit fullscreen mode

6. Complete Code Implementation: Image Processing Application

The following is the complete implementation of an image processing application combined with multithreading task scheduling, priority setting, and performance monitoring:

@Entry
@Component
struct ImageProcessor {
    @State resultLog: Array<string> = []
    build() {
        Column() {
            Button('Start image processing')
            .onClick(() => {
                    this.startImageProcessing();
                })
            // Display processing results and logs
            ForEach(this.resultLog, (log) => {
                Text(log)
            })
        }
    }
    startImageProcessing() {
        const image = this.createDummyImageData(); // Assume there is a method to generate virtual image data
        processImageWithMonitoring(image);
    }
    createDummyImageData(): ArrayBuffer {
        return new ArrayBuffer(4096); // Simulate 4KB image data
    }
}
Enter fullscreen mode Exit fullscreen mode

7. Summary

So far, we have designed and implemented a multithreading image processing application based on ArkTS, demonstrating how to process image data in slices and concurrently through TaskGroup, and how to optimize task scheduling by setting task priorities. Through performance monitoring, we can analyze the execution time of each task and find performance bottlenecks for targeted optimization.
This case shows the powerful concurrent processing ability and performance optimization tools of ArkTS, which can help developers effectively utilize the computing power of multi-core CPUs in CPU-intensive tasks and improve the overall performance of the system.

Top comments (0)