在flutter中如果要使用线程,需要借助Isolate来实现。

简介

在Flutter中,Isolate是一种轻量级的线程解决方案,用于在应用程序中执行并发任务。Isolate可以被认为是独立于主线程的工作单元,它们可以在后台执行任务而不会阻塞应用程序的用户界面。

Isolate提供了多线程编程的能力,允许开发者在应用程序中同时执行多个任务,从而提高应用程序的性能和响应能力。每个Isolate都有自己独立的内存空间和执行环境,它们之间可以通过消息传递进行通信。

在Flutter中,可以使用dart:isolate库来创建和管理Isolate。通过创建一个Isolate对象,可以指定要执行的任务代码。Isolate可以执行耗时的计算、网络请求、文件操作等任务,而不会阻塞应用程序的主线程。

与其他线程解决方案相比,Isolate的一个重要特点是它们之间的内存是隔离的,这意味着每个Isolate都有自己独立的内存空间,它们之间不能直接共享数据。为了在Isolate之间传递数据,可以使用消息传递机制,即将数据打包成消息发送给目标Isolate,并在接收端解包处理。

使用Isolate可以提高应用程序的性能和响应能力,特别是在处理大量计算密集型任务或需要与外部资源进行交互的场景中。然而,需要注意的是,Isolate的创建和销毁都会带来一定的开销,因此在使用Isolate时需要权衡资源消耗和性能提升之间的平衡。

总而言之,Isolate是Flutter中的一种轻量级线程解决方案,用于在应用程序中执行并发任务。它们可以独立于主线程执行任务,并通过消息传递机制进行通信。使用Isolate可以提高应用程序的性能和响应能力,特别是在处理计算密集型任务或需要与外部资源进行交互的场景中。

基本使用

匿名函数使用

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

参数会被传递到匿名函数中,并被匿名函数所使用。

普通函数

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

这个一定要注意,在flutter中使用时newThread1不能写在主进程所在的类里。否则会提示[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)

子线程向主线程通信

// 创建一个额外线程
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.
    );
  }
}

在这里插入图片描述
主线程向子线程通信
在子线程执行期间,主线程也可以向子线程进行通信。基于上面的代码,进行简单修改

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);
      }
    });
  }

怎么理解呢?

可以理解为:我找了一个人干活,我把自己的手机号给他了,但是忘记跟他要手机号了,因此只能他给我打电话,而我不能给他打电话。
当他向我打电话后,我存下了他的手机号,后续我就可以给他打电话了。
也就是下面内容:
子线程要想向主线程通信,需要获取到主线程的receivePort.sendPort,因此在子线程创建时,主线程将自己的receivePort.sendPort传递给子线程。
同理,主线程要想向子线程通信也需要拿到子线程的receivePort.sendPort,因此在子线程向主线程通信后,需要将自己的端口号给主线程。

只有主线程、子线程都拿到对方的receivePort.sendPort 才可以进行互相通信。

在这里插入图片描述

示例

主进程创建多个子进程,子进程进行请求。当子进程请求完成后向主进程发起通知。主进程需要等待多个子进程全都发起通知后,再进行后续操作。

void getNovelCatalogMultiThread(Map mes) async {
  try {
    /// 获取任务和端口
    List<String> requestUrlList = mes["request"];
    List<Future<Response>> requests = [];
    for (String url in requestUrlList) {
      requests.add(dio.get(url));
    }

    /// 端口
    SendPort sendPort = mes["port"];

    /// 任务序号
    int taskIndex = mes["taskIndex"];
    List<Response> responses = await Future.wait(requests);
    //print("任务完成");
    /// 向主进程发消息
    List<String> list = [];
    for (Response response in responses) {
      list.add(response.data);
    }
    sendPort.send({
      "taskIndex": taskIndex,
      "list": list,
    });
  } catch (e) {
    logger.e(e);
    RecordErrorLog.writeLog(
        errorType: ErrorType.novel,
        errorMsg: "eqzuzu——getNovelCatalogMultiThread,获取目录失败");
  }
}
/// 将请求地址按照每5个一组进行划分
List requests = BaseUtil.divideList(catalogUrlList, 5);
/// 使用多线程进行请求
List receivePortList = [];
for (int i = 0; i < requests.length; i++) {
  receivePortList.add(ReceivePort());
}

/// 初始化Future列表,存储每一个异步任务
final isolateFutures = <Future<dynamic>>[];
for (int i = 0; i < requests.length; i++) {
  List<String> request = requests[i];

  /// 用于主线程与子线程通信
  final receivePort = ReceivePort();

  ///用于标记异步操作何时完成。当从Isolate接收到数据时,将使用这个Completer来完成Future。
  final completer = Completer<dynamic>();
  isolateFutures.add(completer.future);
  await Isolate.spawn(getNovelCatalogMultiThread, {
    "request": request,
    "port": receivePort.sendPort,
    'taskIndex': i
  });
  receivePort.listen((message) {
    completer.complete(message);
    receivePort.close();
  });
}

/// 获取所有Isolate的执行结果
final results = await Future.wait(isolateFutures);
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐