包含数千个文件的 DownloadFile 阻塞了应用程序界面
DownloadFile with thousands of files is blocking the application interface
我正在使用此代码下载 15019 张 JPG 格式的图片:
for (const item of requestUrl) {
const optios = {
fromUrl: item.urlPath,
toFile: item.path,
};
downloadFile(optios).promise;
}
❌ 应用界面被屏蔽
注意重要:我知道RN只在一个线程上运行,所以我在前台和后台使用运行实现了Headless JS,它可以工作,但是同样的行为,接口被阻塞了。
谁能帮我解决这个问题?
react-native-fs": "2.13.3
本机反应:0.59.9
你在使用某种状态管理吗? Redux,上下文,redux-saga/redux-thunk?
您可以使用react-native-background-downloader尝试后台下载
如果您使用的是 redux 和 redux-saga,您可以使用 fork 下载它,这样它就不会阻塞。
此外,如果您正在下载图像并将其存储在 props 或 state 中,它可能会为每个下载的图像重新渲染。
react-native-fs 在其原生实现中完成所有工作,并通过 react-native 桥公开异步 api。因此,在通过 react-native-js 下载文件时,你的 js 线程大部分时间都是空闲的,你的 js 是在前台还是后台 运行ning 并不重要。
实际问题
您实施的实际问题是 downloadFile
会立即触发下载和 return Promise。因此,您的 for 循环将 运行 几乎立即完成所有 15k 次迭代,在您的应用程序的本机部分开始 15k 并发下载。这可能足以使整个应用程序被阻止的任何设备变慢。
解决方案
如果您需要下载很多文件,您应该实施某种队列和流水线来限制并发下载的数量。 (即使设备可以毫无问题地处理 15k 并发下载,下载也会受到可用带宽的限制。同时进行约 3 次下载可能足以一直使用最大带宽。)
可能的流水线解决方案如下所示:
(以下代码未 运行 或测试)
/**
*
* @param {item} The item to download
* @param {item.urlPath} The url to download the item from
* @param {item.path} The local path to download the item to
* @returns the Promise returned by react-native-fs downloadFile
*/
const downloadItem = ({ urlPath, path }) => {
const options = {
fromUrl: urlPath,
toFile: path
}
return downloadFile(options);
}
/**
*
* @param {Array} Array of the items to download
* @param {integer} maximum allowed concurrent downloads
* @returns {Promise} which resolves when all downloads are done
*/
const downloadPipelined = (items, maxConcurrency = 3) => {
// Clone the items Array because we will mutate the queue
const queue = [...items];
/**
* Calling this function will
* - donwload one item after another until the queue is empty
* - return a promise which resolves if the queue is empty and
* the last download started from this workerFn is finished
*/
const workerFn = async () => {
while (queue.length > 0){
await downloadItem(queue.shift());
}
}
/**
* starts maxConcurrency of these workers concurrently
* and saves the promises returned by them to an array
*/
const workers = Array(maxConcurrency).fill(workerFn());
/**
* will resolve when all worker promises have resolved
* (all downloads are done)
* reject if one rejects
*/
return Promise.all(workers);
}
我正在使用此代码下载 15019 张 JPG 格式的图片:
for (const item of requestUrl) {
const optios = {
fromUrl: item.urlPath,
toFile: item.path,
};
downloadFile(optios).promise;
}
❌ 应用界面被屏蔽
注意重要:我知道RN只在一个线程上运行,所以我在前台和后台使用运行实现了Headless JS,它可以工作,但是同样的行为,接口被阻塞了。
谁能帮我解决这个问题?
react-native-fs": "2.13.3
本机反应:0.59.9
你在使用某种状态管理吗? Redux,上下文,redux-saga/redux-thunk? 您可以使用react-native-background-downloader尝试后台下载
如果您使用的是 redux 和 redux-saga,您可以使用 fork 下载它,这样它就不会阻塞。
此外,如果您正在下载图像并将其存储在 props 或 state 中,它可能会为每个下载的图像重新渲染。
react-native-fs 在其原生实现中完成所有工作,并通过 react-native 桥公开异步 api。因此,在通过 react-native-js 下载文件时,你的 js 线程大部分时间都是空闲的,你的 js 是在前台还是后台 运行ning 并不重要。
实际问题
您实施的实际问题是 downloadFile
会立即触发下载和 return Promise。因此,您的 for 循环将 运行 几乎立即完成所有 15k 次迭代,在您的应用程序的本机部分开始 15k 并发下载。这可能足以使整个应用程序被阻止的任何设备变慢。
解决方案
如果您需要下载很多文件,您应该实施某种队列和流水线来限制并发下载的数量。 (即使设备可以毫无问题地处理 15k 并发下载,下载也会受到可用带宽的限制。同时进行约 3 次下载可能足以一直使用最大带宽。)
可能的流水线解决方案如下所示:
(以下代码未 运行 或测试)
/**
*
* @param {item} The item to download
* @param {item.urlPath} The url to download the item from
* @param {item.path} The local path to download the item to
* @returns the Promise returned by react-native-fs downloadFile
*/
const downloadItem = ({ urlPath, path }) => {
const options = {
fromUrl: urlPath,
toFile: path
}
return downloadFile(options);
}
/**
*
* @param {Array} Array of the items to download
* @param {integer} maximum allowed concurrent downloads
* @returns {Promise} which resolves when all downloads are done
*/
const downloadPipelined = (items, maxConcurrency = 3) => {
// Clone the items Array because we will mutate the queue
const queue = [...items];
/**
* Calling this function will
* - donwload one item after another until the queue is empty
* - return a promise which resolves if the queue is empty and
* the last download started from this workerFn is finished
*/
const workerFn = async () => {
while (queue.length > 0){
await downloadItem(queue.shift());
}
}
/**
* starts maxConcurrency of these workers concurrently
* and saves the promises returned by them to an array
*/
const workers = Array(maxConcurrency).fill(workerFn());
/**
* will resolve when all worker promises have resolved
* (all downloads are done)
* reject if one rejects
*/
return Promise.all(workers);
}