Flutter 知识点:验证 Future 对象提供的异步操作
Flutter 知识点:Future 对象提供的异步操作并不是一种多线程机制
如果在 Flutter 中遇到了跟异步操作相关的卡顿,那么我们首先要做的便是加深自己对 Flutter 单线程模型 Single-Threaded(或者是 Dart 的单线程模型)的理解。
Future 并不是一种多线程机制
Flutter 中由 Future 对象以及 async 和 await 关键字提供的异步操作,并不是一种多线程机制,它只是一种非阻塞机制,此时的异步操作并没有在另外一条线程中执行,它还是在当前线程执行的。这里可以类比 JavaScript 中的 Promise,同时 Dart 的 Single-Threaded 也可以类比 JavaScript 的 Single-Threaded 来理解学习。那么如何通过代码的形式来验证这些概念呢?下面我们会通过一个简单的网络请求代码来进行验证。
异步编程并不一定意味着多线程。(async programming does not necessarily mean multi-threaded.)
在 iOS 和 Android 开发中我们直接有 Thread 类来表示一个线程,在 Flutter 中则是没有这样的类的,这对我们理解 Flutter 中的线程概念造成了一些困难,不过这里我们可以借助 Isolate 来近似理解,一个 Isolate 对应一个线程。
验证 Future 对象提供的异步操作还在当前线程执行
下面我们主要依靠打印 Isolate 对象信息来验证在 Flutter 中 Future 对象提供的异步操作还是在当前线程执行。我们准备了两段功能完全相同的 iOS 和 Flutter 代码,功能都是完成一个简单的网络请求,通过 log 输出可以看出 iOS 在网络请求过程中有子线程出现,而 Flutter 则一直只有主线程。
下面是 iOS 下的一个最简单的网络请求示例代码:
func getUsers() {
print("tag 1️⃣ current thread:\(Thread.current)")
let url = URL(string: "https://reqres.in/api/user?page=1")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
let dataString = String(data: data!, encoding: .utf8)
print("🌹打印请求返回的数据:\(dataString ?? "")");
print("tag 2️⃣ current thread:\(Thread.current)")
DispatchQueue.main.async {
print("tag 3️⃣ current thread:\(Thread.current)")
// 在主线程中更新 UI
}
}
task.resume()
// 对照输出
print("tag 4️⃣ current thread:\(Thread.current)")
}
// 控制台打印
tag 1️⃣ current thread:<_NSMainThread: 0x600001708040>{number = 1, name = main}
tag 4️⃣ current thread:<_NSMainThread: 0x600001708040>{number = 1, name = main}
🌹打印请求返回的数据:{"page":1,"per_page":6,"..."}
tag 2️⃣ current thread:<NSThread: 0x6000017002c0>{number = 5, name = (null)}
tag 3️⃣ current thread:<_NSMainThread: 0x600001708040>{number = 1, name = main}
我们在主线程中调用 getUsers
函数,首先看到 tag 1️⃣ 和 tag 4️⃣ 被执行,且当前都是主线程,tag 4️⃣ 并未等 URLSession.shared.dataTask
网络请求完成后才去执行,而是立即得到了执行,这里可以看出 URLSession.shared.dataTask
也是非阻塞的,这里它和 Future 的非阻塞机制并不相同的,但是呈现的结果则是相同的。然后重点来了,看 tag 2️⃣ 处的打印,它当前并未在主线程,而是在一个子线程中,这里说明 URLSession.shared.dataTask
在子线程发起了网络请求,iOS 为其准备了一条子线程来进行处理后续事宜。那么在 Flutter 中呢?下面看同 iOS 代码结构完全相同的 Flutter 代码:
getUsers() {
debugPrint(
"tag 1️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
const String userUrl = "https://reqres.in/api/user?page=1";
http.get(Uri.parse(userUrl)).then(
(response) {
final result = jsonDecode(response.body);
debugPrint("🌹打印请求返回的数据:$result");
debugPrint(
"tag 2️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
},
);
debugPrint(
"tag 4️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
}
// 控制台打印
flutter: tag 1️⃣ current isolate hashCode: 310700307 current isolate name: main
flutter: tag 4️⃣ current isolate hashCode: 310700307 current isolate name: main
flutter: 🌹打印请求返回的数据:{page: 1, per_page: 6,...
flutter: tag 2️⃣ current isolate hashCode: 310700307 current isolate name: main
我们在主 Isolate 中调用 getUsers
函数,同样的看到 tag 1️⃣ 和 tag 4️⃣ 首先被执行,且都打印了当前是主 Isolate,即使 http.get(Uri.parse(userUrl))
请求完成后回调执行打印 tag 2️⃣ 依然在主 Isolate 中。这里 http.get(Uri.parse(userUrl))
函数返回的就是一个 Future 对象,可以看到在网络请求的整个过程中都没有出现子线程,但是这样看好像不明显,因为 http.get(Uri.parse(userUrl))
函数返回的 Future 对象的异步操作执行过程被包裹起来了。下面我们会直接自己手动创建 Future 对象来验证,在开始之前,我们再测试一种情况,让你切实看到:iOS 的网络请求自行去到了子线程去执行,而 Flutter 中通过 Future 添加的网络请求,只是被添加到了 Dart 的事件循环的事件队列中,等待着下一个循环得到执行。这里我们改造一下 getUsers
函数调用处,直接在 getUsers
函数下添加一行 sleep
:
// ios
{
getUsers()
sleep(5);
}
// flutter
{
getUsers();
sleep(const Duration(seconds: 5));
}
运行代码后我们可以发现 iOS 和 Flutter 的天差地别,在 iOS 中不管有没有这个 sleep
函数的调用,getUsers
函数中的网络请求都可以直接发起,并正常打印请求响应。而在 Flutter 中,必须等到 5 秒以后,网络请求才能正常发起执行。那么从这种表现下,你能得出什么结论呢?
由于 Flutter 示例代码中 http.get(Uri.parse(userUrl))
函数返回的 Future 对象的异步操作执行过程被包裹起来了,所有我们看的不那么明显,下面我们直接自己手动创建一个 Future 对象,并尝试让它的异步行为执行耗时操作看看:
getUsers() {
debugPrint(
"tag 1️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
Future(() {
debugPrint(
"tag 2️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
return 123;
}).then((value) {
debugPrint(
"tag 3️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
});
debugPrint(
"tag 4️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
}
// 控制台打印
flutter: tag 1️⃣ current isolate hashCode: 979228963 current isolate name: main
flutter: tag 4️⃣ current isolate hashCode: 979228963 current isolate name: main
flutter: tag 2️⃣ current isolate hashCode: 979228963 current isolate name: main
flutter: tag 3️⃣ current isolate hashCode: 979228963 current isolate name: main
我们自己直接创建 Future 对象,看到 tag 4️⃣ 早于 tag 2️⃣、tag 3️⃣ 执行,且四个打印完全都在主 Isolate 中。然后可以再做个实验,直接在 tag 2️⃣ 上面添加一行 sleep(const Duration(seconds: 5));
然后执行,可以看到 tag 1️⃣ 和 tag 4️⃣ 执行后 UI 卡顿了 5 秒完全不可交互,5 秒后卡顿才结束 UI 才恢复正常,tag 2️⃣ 和 tag 3️⃣ 才得到执行。看到这里既可验证了:Future 异步操作都是在主 Isolate 中执行的,并没有开辟新线程,且异步操作中的耗时操作会直接导致 Flutter 中的 UI 卡顿。
对比 Isolate 异步操作(在新线程中异步操作)
既然都看到这里了,作为对照组,我们试一下在 Flutter 中创建新 Isolate 开辟新线程的能力:
void createIsolate() async {
debugPrint(
"tag 1️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
var result = await compute(doWork, "");
debugPrint(
"tag 2️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
}
String doWork(String value) {
debugPrint(
"tag 3️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
// 模拟耗时 5 秒
sleep(const Duration(seconds: 5));
debugPrint(
"tag 4️⃣ current isolate hashCode: ${Isolate.current.hashCode} current isolate name: ${Isolate.current.debugName}",
);
return "";
}
// 控制台打印
flutter: tag 1️⃣ current isolate hashCode: 1051875222 current isolate name: main
flutter: tag 3️⃣ current isolate hashCode: 473982230 current isolate name: Closure: (String) => String from Function 'doWork': static.
// 延迟了 5 秒后才打印 4️⃣ 和 2️⃣,且这个过程中 APP 一直是正常运行无 UI 阻塞的
flutter: tag 4️⃣ current isolate hashCode: 473982230 current isolate name: Closure: (String) => String from Function 'doWork': static.
flutter: tag 2️⃣ current isolate hashCode: 1051875222 current isolate name: main
通过控制台打印可以看出 compute
构建了新的 Isolate,开辟了新的线程执行异步操作,且这个 sleep(const Duration(seconds: 5));
模拟耗时操作 5 秒是完全不阻塞 UI 线程的,App 正常运行 UI 可交互。不像 Future 中添加耗时操作会直接阻塞主线程导致 UI 不可交互。那么既然都看到这里了,你能分清 Future 和 Isolate 的各自使用场景了吗?
参考链接
参考链接:🔗
转载自:https://juejin.cn/post/7366824731955740724