工人在Chrome中阻止UI线程

Jos*_*lsh 10 javascript multithreading google-chrome web-worker evaporate.js

我正在构建一个使用EvaporateJS的Web应用程序,使用Multipart上传将大文件上传到Amazon S3.我注意到一个问题,每次启动新块时,浏览器都会冻结约2秒钟.我希望用户能够在上传过程中继续使用我的应用程序,这种冻结会让人感觉很糟糕.

我使用Chrome的时间轴来查看造成这种情况的原因,发现它是SparkMD5的散列.所以我把整个上传过程转移到了一个工作者,我认为这会解决问题.

那么问题现在已经在Edge和Firefox中得到修复,但Chrome仍然存在完全相同的问题.

这是我的时间轴的截图: 时间线

正如你所看到的,在冻结期间,我的主线程基本上什么也没做,在此期间运行<8ms的JavaScript.所有工作都在我的工作线程中进行,即使只运行约600ms左右,而不是我的框架所需的1386ms.

我真的不确定是什么导致了这个问题,是否有任何与工人有关的问题我应该注意什么?

这是我的工人的代码:

var window = self; // For Worker-unaware scripts

// Shim to make Evaporate work in a Worker
var document = {
    createElement: function() {
        var href = undefined;

        var elm = {
            set href(url) {
                var obj = new URL(url);
                elm.protocol = obj.protocol;
                elm.hostname = obj.hostname;
                elm.pathname = obj.pathname;
                elm.port = obj.port;
                elm.search = obj.search;
                elm.hash = obj.hash;
                elm.host = obj.host;
                href = url;
            },
            get href() {
                return href;
            },
            protocol: undefined,
            hostname: undefined,
            pathname: undefined,
            port: undefined,
            search: undefined,
            hash: undefined,
            host: undefined
        };

        return elm;
    }
};

importScripts("/lib/sha256/sha256.min.js");
importScripts("/lib/spark-md5/spark-md5.min.js");
importScripts("/lib/url-parse/url-parse.js");
importScripts("/lib/xmldom/xmldom.js");
importScripts("/lib/evaporate/evaporate.js");

DOMParser = self.xmldom.DOMParser;

var defaultConfig = {
    computeContentMd5: true,
    cryptoMd5Method: function (data) { return btoa(SparkMD5.ArrayBuffer.hash(data, true)); },
    cryptoHexEncodedHash256: sha256,
    awsSignatureVersion: "4",
    awsRegion: undefined,
    aws_url: "https://s3-ap-southeast-2.amazonaws.com",
    aws_key: undefined,
    customAuthMethod: function(signParams, signHeaders, stringToSign, timestamp, awsRequest) {
        return new Promise(function(resolve, reject) {
            var signingRequestId = currentSigningRequestId++;

            postMessage(["signingRequest", signingRequestId, signParams.videoId, timestamp, awsRequest.signer.canonicalRequest()]);
            queuedSigningRequests[signingRequestId] = function(signature) {
                queuedSigningRequests[signingRequestId] = undefined;
                if(signature) {
                    resolve(signature);
                } else {
                    reject();
                }
            }
        });
    },
    //logging: false,
    bucket: undefined,
    allowS3ExistenceOptimization: false,
    maxConcurrentParts: 5
}

var currentSigningRequestId = 0;
var queuedSigningRequests = [];

var e = undefined;
var filekey = undefined;
onmessage = function(e) {
    var messageType = e.data[0];
    switch(messageType) {
        case "init":
            var globalConfig = {};
            for(var k in defaultConfig) {
                globalConfig[k] = defaultConfig[k];
            }
            for(var k in e.data[1]) {
                globalConfig[k] = e.data[1][k];
            }

            var uploadConfig = e.data[2];

            Evaporate.create(globalConfig).then(function(evaporate) {
                var e = evaporate;

                filekey = globalConfig.bucket + "/" + uploadConfig.name;

                uploadConfig.progress = function(p, stats) {
                    postMessage(["progress", p, stats]);
                };

                uploadConfig.complete = function(xhr, awsObjectKey, stats) {
                    postMessage(["complete", xhr, awsObjectKey, stats]);
                }

                uploadConfig.info = function(msg) {
                    postMessage(["info", msg]);
                }

                uploadConfig.warn = function(msg) {
                    postMessage(["warn", msg]);
                }

                uploadConfig.error = function(msg) {
                    postMessage(["error", msg]);
                }

                e.add(uploadConfig);
            });
            break;

        case "pause":
            e.pause(filekey);
            break;

        case "resume":
            e.resume(filekey);
            break;

        case "cancel":
            e.cancel(filekey);
            break;

        case "signature":
            var signingRequestId = e.data[1];
            var signature = e.data[2];
            queuedSigningRequests[signingRequestId](signature);
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,它依赖于调用线程为其提供AWS公钥,AWS Bucket Name和AWS区域,AWS Object Key和输入File对象,这些都在"init"消息中提供.当需要签名时,它会向父线程发送'signingRequest'消息,一旦从我的API签名端点获取签名,就会在签名中提供签名.

ell*_*ben 4

我无法给出一个很好的例子,也无法分析您仅使用 Worker 代码所做的事情,但我强烈怀疑该问题与主线程上块的读取或您正在执行的某些意外处理有关。在主线程上的块上执行操作。postMessage也许发布调用Worker 的主线程代码?

如果我现在正在调试它,我会尝试将您的FileReader操作移至 Worker 中。如果您不介意 Worker 在加载块时阻塞,您也可以使用FileReaderSync.

评论后更新

生成预签名 URL 是否需要对文件内容 + 元数据 + 密钥进行哈希处理?散列文件内容将占用块大小的 O(n),并且如果散列是从 读取的第一个操作Blob,则文件内容的加载可能会推迟到散列开始。除非您被迫将签名保留在主线程中(您不信任具有关键材料的工作人员?),否则这将是带入工作人员的另一件好事。

如果将签名移到 Worker 中太多,您可以让 Worker 执行某些操作来强制读取Blob和/或将ArrayBufferUint8Array或您拥有的)文件内容传递回主线程进行签名;这将确保读取块不会发生在主线程上。