Flutter: Simple use of multi-threaded Isolate

If you want to use threads in flutter, you need to use Isolate to achieve it.

Introduction

In Flutter, Isolate is a lightweight threading solution for performing concurrent tasks in applications. Isolates can be thought of as units of work that are independent of the main thread and can perform tasks in the background without blocking the application's user interface.

Isolate provides multi-threaded programming capabilities, allowing developers to perform multiple tasks simultaneously in applications, thereby improving application performance and responsiveness. Each Isolate has its own independent memory space and execution environment, and they can communicate with each other through message passing.

In Flutter, you can use the dart:isolate library to create and manage Isolates. By creating an Isolate object, you can specify the task code to be executed. Isolate can perform time-consuming calculations, network requests, file operations and other tasks without blocking the application's main thread.

Compared with other thread solutions, an important feature of Isolate is that the memory between them is isolated, which means that each Isolate has its own independent memory space and data cannot be shared directly between them. In order to transfer data between isolates, the message passing mechanism can be used, that is, the data is packaged into a message and sent to the target isolate, and is unpacked and processed at the receiving end.

Using Isolate can improve the performance and responsiveness of your application, especially in scenarios where you are dealing with a large number of computationally intensive tasks or need to interact with external resources. However, it should be noted that the creation and destruction of Isolate will bring certain overhead, so when using Isolate, you need to weigh the balance between resource consumption and performance improvement.

To summarize, Isolate is a lightweight threading solution in Flutter for performing concurrent tasks in your application. They can perform tasks independently of the main thread and communicate through a message passing mechanism. Using Isolate can improve the performance and responsiveness of your application, especially in scenarios where you are dealing with computationally intensive tasks or need to interact with external resources.

Basic use

Anonymous function usage

void _incrementCounter() {
    
    
  setState(() {
    
    
    _counter++;
  });
  // 匿名线程
  Isolate.spawn((message) {
    
    
    print("匿名函数线程:$message");
  }, '哈哈哈');
}

The parameters will be passed to the anonymous function and used by the anonymous function.

Ordinary function

 void _incrementCounter() {
    
    
   setState(() {
    
    
     _counter++;
   });
   // 普通线程
   Isolate.spawn(newThread1, "普通线程");
 }
// 创建一个额外线程
void newThread1(String message){
    
    
  print(message);
}

You must pay attention to this. When using it in flutternewThread1 cannot be written in the class where the main process is located. Otherwise it will prompt[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message: object is unsendable - Library:'dart:async' Class: _AsyncCompleter@4048458 (see restrictions listed at SendPort.send() documentation for more information)

Child thread communicates with main thread

// 创建一个额外线程
void newThread1(SendPort mainThreadPort) {
    
    
  int num = 0;
  Timer.periodic(const Duration(seconds: 1), (timer) {
    
    
    // 每隔1秒num加1
    num += 1;
    mainThreadPort.send(num);
    if (num == 10) {
    
    
      // 向主进程发消息执行完成
      mainThreadPort.send('stop');
    }
  });
}

class _MyHomePageState extends State<MyHomePage> {
    
    
  final receivePort = ReceivePort();

  void _incrementCounter() async {
    
    
    // 子线程
    final isolate = await Isolate.spawn(newThread1, receivePort.sendPort);

    // 监听子线程发送的消息
    receivePort.listen((message) {
    
    
      print("子线程发送的消息:$message");
      if (message == 'stop') {
    
    
        // 执行完成则停止显示
        print("线程执行完成");
        isolate.kill(priority: Isolate.immediate);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const Center(child: Text("多线程")),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Insert image description here
The main thread communicates with the child thread
During the execution of the child thread, the main thread can also communicate with the child thread. Based on the above code, make simple modifications

void newThread1(SendPort mainThreadPort) {
    
    
  int num = 0;
  ReceivePort receivePort = ReceivePort();
  Timer.periodic(const Duration(seconds: 1), (timer) {
    
    
    // 每隔1秒num加1
    num += 1;
    mainThreadPort.send(num);
    if(num==1){
    
    
      // 跟主进程类似,将receivePort.sendPort传递给主进程,主进程才能向子进程通信
      mainThreadPort.send(receivePort.sendPort);
    }
    if (num == 10) {
    
    
      // 向主进程发消息执行完成
      mainThreadPort.send('stop');
    }
  });
  // 接收主进程发送的消息
  receivePort.listen((message) {
    
    
    print("收到了主进程的消息:$message");
  });
}
  void _incrementCounter() async {
    
    
    // 子线程
    final isolate = await Isolate.spawn(newThread1, receivePort.sendPort);
    late SendPort childSendPort;

    // 监听子线程发送的消息
    receivePort.listen((message) {
    
    
      print("子线程发送的消息:$message");
      if (message is SendPort) {
    
    
        childSendPort = message;
      }
      if (message == 6) {
    
    
        childSendPort.send("加油,马上完成了");
      }
      if (message == 'stop') {
    
    
        // 执行完成则停止显示
        print("线程执行完成");
        isolate.kill(priority: Isolate.immediate);
      }
    });
  }

How to understand it?

can be understood as: I found a person to work, and I gave him my mobile phone number, but I forgot to ask him for his mobile phone number, so he could only call me, but I couldn't give it to him. Call up.
After he called me, I saved his mobile phone number so that I could call him later.
That is, the following content:
If the child thread wants to communicate with the main thread, it needs to obtain the receivePort.sendPort of the main thread, so in When the child thread is created, the main thread passes its ownreceivePort.sendPort to the child thread.
In the same way, if the main thread wants to communicate with the sub-thread, it also needs to get the receivePort.sendPort of the sub-thread. Therefore, after the sub-thread communicates with the main thread, it needs to get its own The port number given to the main thread.

Only when the main thread and sub-thread have obtained each other's receivePort.sendPort can they communicate with each other.

Insert image description here

Thread Pool

I don’t know if the demo I wrote counts. After all, I haven’t had much contact with the thread pool.

Introduction

Thread pool is a technology for managing and reusing threads. It consists of a collection of threads that can be used to perform multiple tasks without the need to create a new thread for each task.

There is a task queue in the thread pool to store tasks to be executed. When a new task arrives, the thread pool will take out a thread from the task queue to execute the task. When the task execution is completed, the thread will return to the thread pool and can continue to execute new tasks.

Using a thread pool has the following advantages:

  1. Improve system performance: Due to the large overhead of creating and destroying threads, the thread pool can avoid frequent creation and destruction of threads, thereby improving system performance.
  2. Improve task execution efficiency: The thread pool can execute multiple tasks concurrently, thereby improving task execution efficiency.
  3. Control concurrency: The thread pool can limit the number of threads executing at the same time, thereby controlling the concurrency of tasks and avoiding excessive resource usage.

Case
This case has three roles:

  • Boss, main thread, used to communicate with the foreman thread
  • Foreman, sub-thread, recruits workers, arranges work for workers, and communicates with workers according to the tasks assigned by the boss.
  • Workers, several sub-threads, used to work

work process

[
      {
    
     "taskId":"a1", "number":10},
      {
    
     "taskId":"a2","number":15},
      {
    
     "taskId":"a3","number":20},
      {
    
    "taskId":"a4","number":5}
 ]

There are 4 tasks and 3 workers. The worker starts working (printing a number every 1 second), and when the work is completed (the printed number is equal to number), the worker reports to the foreman, and the foreman arranges new work for the workers who have completed the work.

Rendering
Insert image description here
Insert image description here
Main process (boss)

class _MyHomePageState extends State<MyHomePage> {
    
    
  // 主线程本身的端口
  final receivePort = ReceivePort();
  // 记录子线程的端口
  late SendPort foremanPort;

  late Foreman foreman;

  @override
  void initState() {
    
    
    super.initState();
    // 创建子线程,并将主线程的端口号给子线程
    Foreman.getInstance(receivePort.sendPort);
    // 监听子线程传来的消息
    receivePort.listen((message) {
    
    
      if (message is SendPort) {
    
    
        foremanPort = message;
      } else {
    
    
        // 普通消息
        print("子线程发来的消息:$message");
      }
    });
  }

  void _incrementCounter() async {
    
    
    // 向子线程发消息
    foremanPort.send({
    
    
       "from":"main",
      'type':'initWorker',
      'data':[
        {
    
    
          "taskId":"a1",
          "number":10
        },
        {
    
    
          "taskId":"a2",
          "number":15
        },
        {
    
    
          "taskId":"a3",
          "number":20
        },
        {
    
    
          "taskId":"a4",
          "number":5
        }
      ]
    });
  }

  @override
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const Center(child: Text("多线程")),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Child threads (foreman and workers)

// 包工头

import 'dart:async';
import 'dart:isolate';

class Foreman {
    
    
  // 记录主线程的通信端口
  late SendPort _mainPort;
  // 包工头本身的通信端口
  final ReceivePort _foremanPort = ReceivePort();
  // 任务列表
  List _tasks = [];
  // 工人列表
  final Map _workerMap = {
    
    };
  // 记录当前的任务下标
  int _taskIndex = 0;
  // 创建一个私有类型的实例
  static Foreman? _instance;
  // 私有化构造函数,避免通过构造函数创建实例
  Foreman._init();
  // 获取类的实例
  static Foreman getInstance(SendPort mainPort) {
    
    
    // 未初始化,则进行初始化
    _instance ??= Foreman._init();
    // 记录一下主线程的端口
    _instance!._mainPort = mainPort;
    // 向主进程进行通信
    mainPort.send(_instance!._foremanPort.sendPort);
    // 监听主进程发来的消息
    _instance!._foremanPort.listen((message) {
    
    
      if (message['from'] == 'main') {
    
    
        // 收到来自主线程的消息
        print("主线程发来的信息:$message");
        // 工头开始招人干活
        if (message['type'] == 'initWorker') {
    
    
          _instance!._tasks = message['data'];
          _instance!._initWorker();
        }
      }
      if (message['from'] == 'worker') {
    
    
        // 接收来自工人的端口
        var worker = _instance!._workerMap[message['workerId']];
        // 监听来自工人的消息
        if (message['type'] == 'port') {
    
    
          worker['port'] = message['port'];
          print("收到工人${message['workerId']}的端口");
        }
        if (message['type'] == 'finish') {
    
    
          // 有工人完成工作了,给他安排新工作
          if (_instance!._taskIndex < _instance!._tasks.length) {
    
    
            var task = _instance!._tasks[_instance!._taskIndex];
            // 说明还有工作,安排工作
            worker['port'].send({
    
    
              'workerId': worker['workerId'],
              'taskId': task["taskId"],
              'num': task['number']
            });
            // 更新任务下标
            _instance!._taskIndex += 1;
          }
        }
      }
    });
    return _instance!;
  }

  // 创建工人列表
  _initWorker() {
    
    
    print("任务列表:$_tasks");
    // 默认最大3个线程
    int size = _tasks.length > 3 ? 3 : _tasks.length;
    // 创建工人线程,将工头的端口给工人
    for (int i = 0; i < size; i++) {
    
    
      var isolate = Isolate.spawn(work, {
    
    
        "port": _foremanPort.sendPort,
        "workerId": "worker_$i",
        "taskId": _tasks[i]['taskId'],
        "num": _tasks[i]['number']
      });
      // 记录工人
      _workerMap["worker_$i"] = {
    
    "workerId": "worker_$i", "worker": isolate};
    }
    // 记录下标
    _taskIndex = size;
  }
}

// 工人干活函数
void work(message) {
    
    
  // 记录工头的通信端口
  SendPort foremanPort = message['port'];
  // 工人本身的通信端口
  ReceivePort workerPort = ReceivePort();
  print("message:工人${message['workerId']},开始执行任务${message['taskId']}");
  // 向工头发送消息
  foremanPort.send({
    
    
    "from": "worker",
    "type": 'port',
    "workerId": message['workerId'],
    "port": workerPort.sendPort
  });
  // 开始干活
  toDo(message, foremanPort);
  // 监听来自工头的消息
  workerPort.listen((newMessage) {
    
    
    print("收到来自工头的消息:$newMessage");
    toDo(newMessage, foremanPort);
  });
}

// 工人干的活
void toDo(message, foremanPort) {
    
    
  int num = 0;
  Timer.periodic(const Duration(seconds: 1), (Timer timer) {
    
    
    print("工人${message['workerId']},任务${message['taskId']},$num");
    num += 1;
    if (num == message['num']) {
    
    
      timer.cancel();
      print("工人${message['workerId']},任务:${message['taskId']}完成");
      // 向工头汇报任务完成
      foremanPort.send({
    
    
        "from": "worker",
        "type": "finish",
        "workerId": message['workerId'],
      });
    }
  });
}

Guess you like

Origin blog.csdn.net/weixin_41897680/article/details/134528685