6

Buduję aplikację internetową, która wykorzystuje EvaporateJS do przesyłania dużych plików do Amazon S3 przy użyciu Przesyłania zbiorczego. Zauważyłem problem, w którym za każdym razem, gdy nowy fragment został uruchomiony, przeglądarka zamarzała na ~ 2 sekundy. Chcę, aby użytkownik mógł nadal korzystać z mojej aplikacji, gdy przesyłanie jest w toku, a to zamrażanie sprawia, że ​​to złe wrażenie.Pracownik blokujący wątek UI w Chrome

Użyłem Oś czasu Chrome, aby sprawdzić, co było przyczyną tego i stwierdził, że było to hash SparkMD5. Przeniosłem cały proces przesyłania do modułu roboczego, który według mnie rozwiązałby problem.

Problem został rozwiązany w Edge i Firefox, ale Chrome nadal ma ten sam problem.

Oto zrzut ekranu z mojego osi czasu: Timeline

Jak widać, podczas zamarza mój główny wątek jest w zasadzie nic nie robi, z < 8ms JavaScriptu uruchomionych w tym czasie. Cała praca pojawia się w moim wątku Worker, a nawet to działa tylko przez ~ 600ms, a nie 1386ms, które zajmuje moja klatka.

Naprawdę nie jestem pewien, co jest przyczyną problemu, czy są jakieś kłopoty z pracownikami, o których powinienem wiedzieć?

Oto kod dla mojego Pracownik:

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; 
    } 
} 

Zauważ, że opiera się ona na wątku wywołującego dostarczenie go z AWS Public Key, AWS Bucket Imię i AWS Region, AWS klucza obiektu i obiektu wejściowego pliku , które są zawarte w komunikacie "init". Kiedy potrzebuje czegoś podpisanego, wysyła wiadomość "signingRequest" do wątku nadrzędnego, który powinien dostarczyć podpis w wiadomości "podpisu", gdy zostanie pobrana z punktu końcowego podpisywania mojego interfejsu API.

+0

Czy to w ogóle pomaga? https://github.com/TTLabs/EvaporateJS/issues/257 – user650881

+0

Niezupełnie.Zdaję sobie sprawę z nadmiernego obciążenia EvaporateJS i miałem problemy z wydajnością z nim związane, dlatego zacząłem używać wątków Worker. Moje pytanie brzmi, dlaczego wątek UI jest nadal zamrożony, nawet gdy cała praca dzieje się w Robotniku. –

+0

Z ciekawości, czy kiedykolwiek to rozwiązałeś? – tony19

Odpowiedz

3

Nie mogę podać bardzo dobrego przykładu lub przeanalizować, co robisz tylko z kodem Pracownika, ale mocno podejrzewam, że problem związany jest albo z odczytaniem fragmentu głównego wątku, albo z nieoczekiwanym przetwarzanie, które robisz na kawałku głównego wątku. Może chcesz wysłać do głównego wątku główny kod wątku, który wywołuje postMessage?

Jeśli debugowałem to teraz, spróbuj przenieść twoje operacje FileReader do Robotnika. Jeśli nie masz nic przeciwko blokowaniu modułu Worker podczas ładowania porcji, możesz również użyć FileReaderSync.

Post-komentarze aktualizacji

Czy generowanie presigned URL wymagają Wymieszanie zawartości metadanych pliku + + klucz? Hashowanie zawartości pliku zajmie O (n) w rozmiarze porcji i jest możliwe, jeśli hash jest pierwszą operacją odczytaną z Blob, że ładowanie zawartości pliku może zostać odroczone, dopóki nie rozpocznie się mieszanie. Jeśli nie jesteś zmuszony do trzymania podpisu w głównym wątku (nie ufasz pracownikowi z kluczowym materiałem?), To byłaby kolejna dobra rzecz wnoszona do pracownika.

Jeżeli przeniesienie podpisanie się pracownik jest zbyt wiele, można mieć pracownika coś zrobić, aby zmusić Blob do odczytu i/lub przekazać ArrayBuffer (lub Uint8Array lub Cóżeś) zawartości plików z powrotem do głównego wątek do podpisania; zapewniłoby to, że odczytywanie fragmentu nie występuje w głównym wątku.

+0

Nie mam teraz dostępu do kodu (i nie będę mieć do niego dostępu, dopóki moja nagroda się nie skończy), więc niestety nie mogę opublikować głównego kodu wątku. Zrobię co w mojej mocy, aby zapewnić przegląd tego, jak to działa. Najpierw chcę wyjaśnić, że moje operacje FileReadera rzeczywiście mają miejsce w ramach pracownika. Nie widzisz wywołań w kodzie, który zamieściłem, ponieważ EvaporateJS obsługuje odczytanie pliku, ale w głównym wątku nie używam żadnych funkcji FileReadera lub nawet ładuję EvaporateJS. –

+0

Oto główne operacje na wątek: (1) Użytkownik wybiera swój plik przy użyciu elementu input [type = file]. Główny wątek chwyta nazwę pliku i wysyła żądanie do mojego interfejsu API, aby dowiedzieć się, gdzie przesłać plik. Główny wątek następnie wykorzystuje postMessage do wysłania wiadomości "init" do pracownika. (2) Pracownik rozpoczyna proces przesyłania. Kiedykolwiek potrzebuje adresu URL podanego w AWS, wysyła wiadomość postMessage o nazwie "signingRequest" do głównego wątku. (3) Główny wątek asynchronicznie wywołuje mój interfejs API wraz z żądaniem podpisania. Po podpisaniu wysyła wiadomość postMessage o nazwie "podpis" do pracownika. –

+0

(4) Pracownik przesyła komunikat "postępu" do wątku głównego, gdy postęp jest aktualizowany. (5) Główny wątek następnie wywołuje skrót Angular, aby zaktualizować mój interfejs z nowym postępem. (6) Pracownik przesyła komunikat "kompletny" po zakończeniu przesyłania. –

Powiązane problemy