跳至主要内容

Flutter 并发性:面向 Swift 开发者

Dart 和 Swift 都支持并发编程。本指南将帮助您了解 Dart 中的并发工作原理以及它与 Swift 的比较。通过了解这些内容,您可以创建高性能的 iOS 应用。

在 Apple 生态系统中开发时,某些任务可能需要很长时间才能完成。这些任务包括获取或处理大量数据。iOS 开发人员通常使用 Grand Central Dispatch (GCD) 来使用共享线程池调度任务。使用 GCD,开发人员将任务添加到调度队列中,GCD 决定在哪个线程上执行它们。

但是,GCD 会启动线程来处理剩余的工作项。这意味着您最终可能会拥有大量线程,并且系统可能会过度使用。使用 Swift,结构化并发模型减少了线程数量和上下文切换。现在,每个核心只有一个线程。

Dart 采用单线程执行模型,并支持 `Isolate`、事件循环和异步代码。`Isolate` 是 Dart 对轻量级线程的实现。除非您生成 `Isolate`,否则您的 Dart 代码将在由事件循环驱动的主 UI 线程中运行。Flutter 的事件循环等效于 iOS 主循环——换句话说,即附加到主线程的 Looper。

Dart 的单线程模型并不意味着您必须将所有内容都作为导致 UI 冻结的阻塞操作来运行。相反,请使用 Dart 语言提供的异步功能,例如 `async`/`await`。

异步编程

#

异步操作允许其他操作在其完成之前执行。Dart 和 Swift 都使用 `async` 和 `await` 关键字支持异步函数。在这两种情况下,`async` 表示函数执行异步工作,而 `await` 告诉系统等待函数的结果。这意味着 Dart VM *可能* 会挂起函数(如有必要)。有关异步编程的更多详细信息,请查看 Dart 中的并发

利用主线程/Isolate

#

对于 Apple 操作系统,应用程序开始运行的主要(也称为主)线程。渲染用户界面始终在主线程上发生。Swift 和 Dart 之间的一个区别是
Swift 可能会为不同的任务使用不同的线程,并且 Swift 不会保证使用哪个线程。因此,在 Swift 中调度 UI 更新时,您可能需要确保工作发生在主线程上。

假设您想编写一个异步获取天气并显示结果的函数。

在 GCD 中,要手动将进程调度到主线程,您可以执行以下操作。

首先,定义 `Weather` 枚举

swift
enum Weather: String {
    case rainy, sunny
}

接下来,定义视图模型并将其标记为 @Observable,该模型发布类型为 `Weather?` 的 `result`。使用 GCD 创建后台 `DispatchQueue` 以将工作发送到线程池,然后调度回主线程以更新 `result`。

swift
@Observable class ContentViewModel {
    private(set) var result: Weather?

    private let queue = DispatchQueue(label: "weather_io_queue")
    func load() {
        // Mimic 1 second network delay.
        queue.asyncAfter(deadline: .now() + 1) { [weak self] in
            DispatchQueue.main.async {
                self?.result = .sunny
            }
        }
    }
}

最后,显示结果

swift
struct ContentView: View {
    @State var viewModel = ContentViewModel()
    var body: some View {
        Text(viewModel.result?.rawValue ?? "Loading...")
            .onAppear {
                viewModel.load()
        }
    }
}

最近,Swift 引入了 *actor* 来支持共享可变状态的同步。为了确保工作在主线程上执行,请定义一个标记为 `@MainActor` 的视图模型类,以及一个内部使用 `Task` 调用异步函数的 `load()` 函数。

swift
@MainActor @Observable class ContentViewModel {
  private(set) var result: Weather?
  
  func load() async {
    // Mimic 1 second network delay.
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    self.result = .sunny
  }
}

接下来,使用 `@State` 定义视图模型作为状态,并使用一个可以由视图模型调用的 `load()` 函数

swift
struct ContentView: View {
  @State var viewModel = ContentViewModel()
  var body: some View {
    Text(viewModel.result?.rawValue ?? "Loading...")
      .task {
        await viewModel.load()
      }
  }
}

在 Dart 中,默认情况下所有工作都在主隔离区上运行。要在 Dart 中实现相同的示例,首先创建 `Weather` 枚举

dart
enum Weather {
  rainy,
  windy,
  sunny,
}

然后,定义一个简单的视图模型(类似于在 SwiftUI 中创建的视图模型),以获取天气。在 Dart 中,`Future` 对象表示将来要提供的值。`Future` 类似于 Swift 的 `@Observable`。在此示例中,视图模型中的一个函数返回一个 `Future` 对象

dart
@immutable
class HomePageViewModel {
  const HomePageViewModel();
  Future<Weather> load() async {
    await Future.delayed(const Duration(seconds: 1));
    return Weather.sunny;
  }
}

此示例中的 `load()` 函数与 Swift 代码具有相似之处。Dart 函数被标记为 `async`,因为它使用了 `await` 关键字。

此外,标记为 `async` 的 Dart 函数会自动返回一个 `Future`。换句话说,您不必在标记为 `async` 的函数内部手动创建 `Future` 实例。

对于最后一步,显示天气值。在 Flutter 中,FutureBuilderStreamBuilder
小部件用于在 UI 中显示 `Future` 的结果。以下示例使用 `FutureBuilder`

dart
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  final HomePageViewModel viewModel = const HomePageViewModel();

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      // Feed a FutureBuilder to your widget tree.
      child: FutureBuilder<Weather>(
        // Specify the Future that you want to track.
        future: viewModel.load(),
        builder: (context, snapshot) {
          // A snapshot is of type `AsyncSnapshot` and contains the
          // state of the Future. By looking if the snapshot contains
          // an error or if the data is null, you can decide what to
          // show to the user.
          if (snapshot.hasData) {
            return Center(
              child: Text(
                snapshot.data.toString(),
              ),
            );
          } else {
            return const Center(
              child: CupertinoActivityIndicator(),
            );
          }
        },
      ),
    );
  }
}

有关完整示例,请查看 GitHub 上的 async_weather 文件。

利用后台线程/Isolate

#

Flutter 应用可以在各种多核硬件上运行,包括运行 macOS 和 iOS 的设备。为了提高这些应用程序的性能,您有时必须在不同的核心上并发运行任务。这对于避免使用长时间运行的操作阻塞 UI 渲染尤其重要。

在 Swift 中,您可以利用 GCD 在具有不同服务质量类 (qos) 属性的全局队列上运行任务。这表示任务的优先级。

swift
func parse(string: String, completion: @escaping ([String:Any]) -> Void) {
  // Mimic 1 sec delay.
  DispatchQueue(label: "data_processing_queue", qos: .userInitiated)
    .asyncAfter(deadline: .now() + 1) {
      let result: [String:Any] = ["foo": 123]
      completion(result)
    }
  }
}

在 Dart 中,您可以将计算卸载到工作隔离区,通常称为后台工作程序。常见方案是生成一个简单的 worker 隔离区,并在 worker 退出时在消息中返回结果。从 Dart 2.19 开始,您可以使用 `Isolate.run()` 生成隔离区并运行计算

dart
void main() async {
  // Read some data.
  final jsonData = await Isolate.run(() => jsonDecode(jsonString) as Map<String, dynamic>);`

  // Use that data.
  print('Number of JSON keys: ${jsonData.length}');
}

在 Flutter 中,您还可以使用 `compute` 函数启动一个隔离区来运行回调函数

dart
final jsonData = await compute(getNumberOfKeys, jsonString);

在这种情况下,回调函数是如下所示的顶级函数

dart
Map<String, dynamic> getNumberOfKeys(String jsonString) {
 return jsonDecode(jsonString);
}

您可以在 作为 Swift 开发人员学习 Dart 中找到有关 Dart 的更多信息,以及在 适用于 SwiftUI 开发人员的 Flutter适用于 UIKit 开发人员的 Flutter 中找到有关 Flutter 的更多信息。