fs.readFile parent fs.readFile 回调中的操作不异步执行
fs.readFile operation inside the callback of parent fs.readFile does not execute asynchronously
我读到 fs.readFile
是一个异步操作,读取发生在主线程以外的单独线程中,因此主线程的执行不会被阻塞。所以我想试试下面的东西
// reads take almost 12-15ms
fs.readFile('./file.txt', (err, data) => {
console.log('FIRST READ', Date.now() - start)
const now = Date.now()
fs.readFile('./file.txt', (err, data) => {
// logs how much time it took from the beginning
console.log('NESTED READ CALLBACK', Date.now() - start)
})
// blocks untill 20ms more, untill the above read operation is done
// so that the above read operation is done and another callback is queued in the poll phase
while (Date.now() - now < 20) {}
console.log('AFTER BLOCKING', Date.now() - start)
})
我正在父 fs.readFile
调用的回调中进行另一个 fs.readFile
调用。我期望的是日志 NESTED READ CALLBACK
必须在 AFTER BLOCKING
之后立即到达,因为读取必须在单独的线程中异步完成
结果日志 NESTED READ CALLBACK
在 AFTER BLOCKING
调用后 15 毫秒出现,这表明当我在 while 循环中阻塞时,异步读取操作不知何故从未发生。顺便说一下,while 循环是用来模拟一些需要 20 毫秒才能完成的任务的
这里到底发生了什么?还是我在这里遗漏了一些信息?顺便说一句,我正在使用 windows
在你的 while()
循环中,Javascript 中的任何事件都不会被处理,你的 Javascript 代码的 none 将 运行 (因为你正在阻塞通过循环处理事件循环)。
磁盘操作可以取得一些进展(因为它们在系统线程中做一些工作),但在 while
循环完成之前不会处理它们的结果。但是,因为 fs.readFile()
实际上由三个或更多操作组成,fs.open()
、fs.read()
和 fs.close()
,当事件循环被阻塞时,它可能不会走得太远,因为它需要处理事件以推进其工作的不同阶段。
Turns out the log NESTED READ CALLBACK comes 15ms after the AFTER BLOCKING call, indicating that while I was blocking in the while loop, the async read operation somehow never took place. By the way the while loop is there to model some task that takes 20ms to complete
What exactly is happening here?
fs.readFile()
不是单一的整体操作。相反,它由 fs.open()
、fs.read()
和 fs.close()
组成,它们的顺序是 运行 in user-land Javascript 在主线程中。因此,当您使用 while()
循环阻塞主线程时,fs.readFile()
无法取得很大进展。可能发生的事情是您启动第二个 fs.readFile()
操作,然后启动 fs.open()
操作。它被发送到 libuv 线程池中的 OS 线程。然后,您使用 while()
循环阻止事件循环。当该循环阻塞事件循环时,fs.open()
完成并且(在 libuv 事件循环内部)一个事件被放入事件队列以调用 fs.open()
调用的完成回调。但是,事件循环被您的循环阻塞,因此无法立即调用回调。因此,完成 fs.readFile()
操作的任何进一步进展都将被阻止,直到事件循环释放并可以再次处理等待事件。
当您的 while()
循环完成时,然后控制 returns 到事件循环并调用 fs.open()
调用的完成回调,然后将启动实际数据的读取来自文件。
仅供参考,您实际上可以在 Github 存储库中自己检查 fs.readFile()
对 here 的代码。如果你按照它的流程,你会看到,从它自己的 Javascript,它首先调用 binding.open()
,这是一个本地代码操作,然后,当它完成并且 Javascript 能够处理通过事件队列的完成事件,然后它将 运行 函数 readFileAfterOpen(...)
将调用 bind.fstat()
(另一个本机代码操作),然后,当它完成并且 Javascript 是能够处理完成事件
通过事件队列,它将调用 `readFileAfterStat(...) ,这将分配一个缓冲区并启动读取操作。
在这里,代码会暂时变得更难理解,因为流程会跳到 read_file_context
对象 here,最终它会调用 read()
并在完成时再次调用 Javascript 能够通过事件循环处理完成事件,它可以进一步推进该过程,最终将文件中的所有字节读入缓冲区,关闭文件,然后调用最终回调。
所有这些细节的重点是说明 fs.readFile()
是如何在 Javascript 中编写的,并且由多个步骤组成(其中一些调用代码将在不同的线程中使用一些本机代码), 但只有当事件循环能够处理新事件时才能从一个步骤前进到下一个步骤。因此,如果您使用 while
循环阻塞事件循环,那么 fs.readFile()
将卡在步骤之间而无法前进。只有当事件循环能够处理事件时,它才能前进并最终完成。
类比
打个简单的比方。你叫你哥帮你一个忙,去三个店给你挑东西吃饭。你给他第一家商店的列表和目的地,然后你让他在第一家商店完成后用你的手机给你打电话 phone,你会给他第二个目的地和那家商店的列表。他前往第一家商店。
与此同时,您在手机上给女朋友打电话 phone 并开始与她进行长时间的交谈。你哥哥在第一家商店打完电话给你打电话,但你没有理会他的电话,因为你还在和你女朋友说话。你的兄弟被困在他的 运行 办事任务上,因为他需要和你谈谈下一步是什么。
在这个类比中,单元格phone有点像事件循环处理下一个事件的能力。如果您阻止他在 phone 上呼叫您的能力,那么他将无法前进到他的下一步(事件循环被阻止)。他对这三个商店的访问就像执行 fs.readfile()
操作所涉及的各个步骤。
我读到 fs.readFile
是一个异步操作,读取发生在主线程以外的单独线程中,因此主线程的执行不会被阻塞。所以我想试试下面的东西
// reads take almost 12-15ms
fs.readFile('./file.txt', (err, data) => {
console.log('FIRST READ', Date.now() - start)
const now = Date.now()
fs.readFile('./file.txt', (err, data) => {
// logs how much time it took from the beginning
console.log('NESTED READ CALLBACK', Date.now() - start)
})
// blocks untill 20ms more, untill the above read operation is done
// so that the above read operation is done and another callback is queued in the poll phase
while (Date.now() - now < 20) {}
console.log('AFTER BLOCKING', Date.now() - start)
})
我正在父 fs.readFile
调用的回调中进行另一个 fs.readFile
调用。我期望的是日志 NESTED READ CALLBACK
必须在 AFTER BLOCKING
之后立即到达,因为读取必须在单独的线程中异步完成
结果日志 NESTED READ CALLBACK
在 AFTER BLOCKING
调用后 15 毫秒出现,这表明当我在 while 循环中阻塞时,异步读取操作不知何故从未发生。顺便说一下,while 循环是用来模拟一些需要 20 毫秒才能完成的任务的
这里到底发生了什么?还是我在这里遗漏了一些信息?顺便说一句,我正在使用 windows
在你的 while()
循环中,Javascript 中的任何事件都不会被处理,你的 Javascript 代码的 none 将 运行 (因为你正在阻塞通过循环处理事件循环)。
磁盘操作可以取得一些进展(因为它们在系统线程中做一些工作),但在 while
循环完成之前不会处理它们的结果。但是,因为 fs.readFile()
实际上由三个或更多操作组成,fs.open()
、fs.read()
和 fs.close()
,当事件循环被阻塞时,它可能不会走得太远,因为它需要处理事件以推进其工作的不同阶段。
Turns out the log NESTED READ CALLBACK comes 15ms after the AFTER BLOCKING call, indicating that while I was blocking in the while loop, the async read operation somehow never took place. By the way the while loop is there to model some task that takes 20ms to complete
What exactly is happening here?
fs.readFile()
不是单一的整体操作。相反,它由 fs.open()
、fs.read()
和 fs.close()
组成,它们的顺序是 运行 in user-land Javascript 在主线程中。因此,当您使用 while()
循环阻塞主线程时,fs.readFile()
无法取得很大进展。可能发生的事情是您启动第二个 fs.readFile()
操作,然后启动 fs.open()
操作。它被发送到 libuv 线程池中的 OS 线程。然后,您使用 while()
循环阻止事件循环。当该循环阻塞事件循环时,fs.open()
完成并且(在 libuv 事件循环内部)一个事件被放入事件队列以调用 fs.open()
调用的完成回调。但是,事件循环被您的循环阻塞,因此无法立即调用回调。因此,完成 fs.readFile()
操作的任何进一步进展都将被阻止,直到事件循环释放并可以再次处理等待事件。
当您的 while()
循环完成时,然后控制 returns 到事件循环并调用 fs.open()
调用的完成回调,然后将启动实际数据的读取来自文件。
仅供参考,您实际上可以在 Github 存储库中自己检查 fs.readFile()
对 here 的代码。如果你按照它的流程,你会看到,从它自己的 Javascript,它首先调用 binding.open()
,这是一个本地代码操作,然后,当它完成并且 Javascript 能够处理通过事件队列的完成事件,然后它将 运行 函数 readFileAfterOpen(...)
将调用 bind.fstat()
(另一个本机代码操作),然后,当它完成并且 Javascript 是能够处理完成事件
通过事件队列,它将调用 `readFileAfterStat(...) ,这将分配一个缓冲区并启动读取操作。
在这里,代码会暂时变得更难理解,因为流程会跳到 read_file_context
对象 here,最终它会调用 read()
并在完成时再次调用 Javascript 能够通过事件循环处理完成事件,它可以进一步推进该过程,最终将文件中的所有字节读入缓冲区,关闭文件,然后调用最终回调。
所有这些细节的重点是说明 fs.readFile()
是如何在 Javascript 中编写的,并且由多个步骤组成(其中一些调用代码将在不同的线程中使用一些本机代码), 但只有当事件循环能够处理新事件时才能从一个步骤前进到下一个步骤。因此,如果您使用 while
循环阻塞事件循环,那么 fs.readFile()
将卡在步骤之间而无法前进。只有当事件循环能够处理事件时,它才能前进并最终完成。
类比
打个简单的比方。你叫你哥帮你一个忙,去三个店给你挑东西吃饭。你给他第一家商店的列表和目的地,然后你让他在第一家商店完成后用你的手机给你打电话 phone,你会给他第二个目的地和那家商店的列表。他前往第一家商店。
与此同时,您在手机上给女朋友打电话 phone 并开始与她进行长时间的交谈。你哥哥在第一家商店打完电话给你打电话,但你没有理会他的电话,因为你还在和你女朋友说话。你的兄弟被困在他的 运行 办事任务上,因为他需要和你谈谈下一步是什么。
在这个类比中,单元格phone有点像事件循环处理下一个事件的能力。如果您阻止他在 phone 上呼叫您的能力,那么他将无法前进到他的下一步(事件循环被阻止)。他对这三个商店的访问就像执行 fs.readfile()
操作所涉及的各个步骤。