Oczekiwanie na wykonanie wielu wątków asynchronicznych w pętli

Oczekiwanie na wykonanie wielu wątków asynchronicznych w pętli
K8
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 69
0

Hej,

Mam taki problem. Wgrywam wiele plików poprzez HTTP Post. Czyli najpierw wskazuje pliki, następnie wywołuję funkcję w takiej postaci:

Kopiuj
uploadFiles(): void {
    let t = this;

    if (t.selectedFiles) {
      for ( let i = 0; i < t.selectedFiles.length; i++) {
        t.upload(t.selectedFiles[i]) ;
      } 

     t._service.getfiles(t.id).subscribe(files => t.files = files); 
    }
  }

upload(file: File): void {
    let t = this;

    t._service.upload(file).subscribe({

    obsługa błędów, progress itd.
    
    });
}

Na samym końcu wywołuję funkcję HTTP GET getfiles(), która pobierze zawartość katalogu do którego wgrywam te pliki w celu weryfikacji, natomiast jeżeli wywołanie tej funkcji umieszczę zaraz za pętlą w funkcji uploadFiles() jak powyżej, wówczas API wali wyjątkiem, że katalog nie istnieje - z prostego powodu - funkcja upload() wykonuje się asynchronicznie i kod jest zwalniany od razu po wywołaniu, a nie wykonaniu. Czy jest jakiś sposób, aby uzależnić wywołanie funkcji od wykonania wcześniejszych?

KK

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
kal800 napisał(a):

Na samym końcu wywołuję funkcję HTTP GET getfiles(), która pobierze zawartość katalogu do którego wgrywam te pliki w celu weryfikacji, natomiast jeżeli wywołanie tej funkcji umieszczę zaraz za pętlą w funkcji uploadFiles() jak powyżej, wówczas API wali wyjątkiem, że katalog nie istnieje - z prostego powodu - funkcja upload() wykonuje się asynchronicznie i kod jest zwalniany od razu po wywołaniu, a nie wykonaniu. Czy jest jakiś sposób, aby uzależnić wywołanie funkcji od wykonania wcześniejszych?

Możesz to obkodzić na dwa sposoby.

  • Albo dodać counter, który będziesz zwiększał i sprawdzał czy wszystkie calle się skończyły
  • Albo zamienisz upload() na Promise, i użyjesz Promise.all().

Ten pierwszy wyglądałby jakoś tak:

Kopiuj
uploadFiles(): void {
    let t = this;

    let counter = 0;

    if (t.selectedFiles) {
      for (let i = 0; i < t.selectedFiles.length; i++) {
        t.upload(t.selectedFiles[i], () => {
           counter++;
           if (counter === t.selectedFiles.length) {
             t._service.getfiles(t.id).subscribe(files => t.files = files); 
           }
        }) ;
      } 

    }
  }

upload(file: File, onSuccess): void {
  let t = this;

  t._service.upload(file).subscribe({
    onSuccess();
  });
}

Ten z Promiseem, jakoś tak:

Kopiuj
uploadFiles(): void {
    let t = this;
    const promises = [];
    if (t.selectedFiles) {
      for ( let i = 0; i < t.selectedFiles.length; i++) {
        promises.append(t.upload(t.selectedFiles[i]));
      } 

      Promise.all(promises).then(() => t._service.getfiles(t.id).subscribe(files => t.files = files));
    }
  }

upload(file: File): void {
  const t = this;
  return new Promise(resolve => {
    t._service.upload(file).subscribe({
      resolve();
    });
  });
}

Pierwszy ma taką wadę, że jeśli t.selectedFiles.length jest 0, to getfiles() się nie wywoła, więc musiałbyś jeszcze to dodać - chyba że pasuje Ci to że dla 0 nie jest wołany nigdy.

K8
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 69
0

Jest prawie dobrze. Wszystko gra, ale dla małej ilości małych plików. Chociaż w 100% to rozwiązanie będzie dotyczyło takiego właśnie scenariusza, to trochę nie daje mi to spokoju. Tym razem wyścigi pojawiają się na poziomie samego filesystemu. Funkcja w API się kończy pomyślnie, ale najwidoczniej jeszcze operacja na FS trwa, bo zaczytuje mi nie cały katalog - w przypadku gdy są 3 duże pliki (ok 600 KB) pokazuje mi tylko jeden, a w przypadku 12 małych (ok 12 KB) pokazuje 11 albo 10. Funkcja wygląda tak:

Kopiuj
 [HttpPost]
        public IActionResult UploadFile()
        {
            try
            {
                var file = Request.Form.Files[0];
                var id = Request.Headers["ID"];

                if (file.Length > 0)
                {
                    var basedir = _settings.PackageTMP;
                    var dir = basedir + id;

                    if (!Directory.Exists(dir))
                        Directory.CreateDirectory(dir);

                    string filePath = Path.Combine(dir, file.FileName);
                    using (Stream fileStream = new FileStream(filePath, FileMode.CreateNew))
                    {
                        file.CopyTo(fileStream);
                    }
                    return Ok();
                }    
                else
                {
                    return BadRequest();
                }
            }
            catch (Exception ex)
            {
                return StatusCode(500, $"Internal server error: {ex}");
            }
        }

Może dać jakiegoś delaya przed wywołaniem Return Ok(); ?

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
kal800 napisał(a):

Może dać jakiegoś delaya przed wywołaniem Return Ok(); ?

Bez sensu.

Jest prawie dobrze. Wszystko gra, ale dla małej ilości małych plików. Chociaż w 100% to rozwiązanie będzie dotyczyło takiego właśnie scenariusza, to trochę nie daje mi to spokoju. Tym razem wyścigi pojawiają się na poziomie samego filesystemu. Funkcja w API się kończy pomyślnie, ale najwidoczniej jeszcze operacja na FS trwa, bo zaczytuje mi nie cały katalog - w przypadku gdy są 3 duże pliki (ok 600 KB) pokazuje mi tylko jeden, a w przypadku 12 małych (ok 12 KB) pokazuje 11 albo 10. Funkcja wygląda tak:

Na pewno dobrze się zapiąłeś pod .subscribe()?

Tzn, na pewno Twój t._service.getfiles(t.id) leci dopiero po tym jak wszystkie poprzednie calle się skończą? Pokaż kod frontu jaki masz.

K8
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 69
0
Kopiuj
uploadFiles(): void {
    let t = this;
    let counter = 0;

    t.message = [];
    if (t.selectedFiles) {
      for ( let i = 0; i < t.selectedFiles.length; i++) {
        t.upload(i, t.selectedFiles[i], () => {
            counter++;
            if (counter === t.selectFiles.length) {
              t._pkgs.get_pkg_tmp_files(t.id).subscribe(files => t.files = files);
            }
        });
      }     
    }
  }

  upload(idx: number, file: File, onSuccess): void {
    let t = this;
    this.progressInfos[idx] = { value: 0, fileName: file.name };
    if (file) {
      
      t._upl.upload(file, t.id).subscribe({
        next: (event: any) => {
          if (event.type === HttpEventType.UploadProgress) {
            this.progressInfos[idx].value = Math.round(100 * event.loaded / event.total);
          } else if (event instanceof HttpResponse) {
            const msg = 'Uploaded the file successfully: ' + file.name;
            this.message.push(msg);
          }
        },
        error: (err: any) => {
          this.progressInfos[idx].value = 0;
          const msg = 'Could not upload the file: ' + file.name;
          this.message.push(msg);
        },
        complete: () => onSuccess()
      });
    }
  }
Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0

@kal800: complete na pewno się odpala, kiedy wróci odpowiedź? Nie odpala się przypadkiem po odpaleniu requesta?

SP
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 33
0

Trochę offtop, ale masz tu wyścig:

Kopiuj
if (!Directory.Exists(dir))
    Directory.CreateDirectory(dir);

ktoś może stworzyć folder między Exists a Create

FG
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 57
0

Użyj operatora forkJoin, a jak wszystko się powiedzie to wywołaj dalej request sprawdzający
https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin

Najpierw w pętli utwórz requesty (nie subskrybuj się do każdego z nich), a potem za pętlą

Kopiuj
const requests = [request1, request2, request3]; (utworzone w pętli)
forkJoin(requests).pipe(
  switchMap(([response1, response2, response3]) => {
   .... tutaj walnij geta (tu się nie musisz subskrybować, bo switchMap sam to zrobi)
  },
  catchError(error => obsłużyć)
).subscribe();

P.S.
W Angularze masz Observable, po co stosować Promise?

P.S.2
Jest taka dobra zasada, żeby metoda subscribe była pusta (dla strumieni masz operator tap jak chcesz coś wykonać)

To tylko schemat ideowy, kompilowany w głowie :)

K8
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 69
0
Riddle napisał(a):

@kal800: complete na pewno się odpala, kiedy wróci odpowiedź? Nie odpala się przypadkiem po odpaleniu requesta?

"Optional. A handler for the execution-complete notification. Delayed values can continue to be delivered to the next handler after execution is complete". - tak to wygląda jeżeli chodzi o dokumentację

spaghetticoder napisał(a):

Trochę offtop, ale masz tu wyścig:

Kopiuj
if (!Directory.Exists(dir))
    Directory.CreateDirectory(dir);

ktoś może stworzyć folder między Exists a Create

Raczej nie - ja tworzę te katalogi z nazwą wygenerowanego GUIDa, ktoś by musiał wskoczyć z tym samym numerem GUID pomiędzy, co jest mało prawdopodobne raczej ;)

FrontendGuy napisał(a):

Najpierw w pętli utwórz requesty (nie subskrybuj się do każdego z nich), a potem za pętlą

Dla mnie Level Expert jeżeli chodzi o front...

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
spaghetticoder napisał(a):

Trochę offtop, ale masz tu wyścig:

Kopiuj
if (!Directory.Exists(dir))
    Directory.CreateDirectory(dir);

ktoś może stworzyć folder między Exists a Create

Raczej nie - ja tworzę te katalogi z nazwą wygenerowanego GUIDa, ktoś by musiał wskoczyć z tym samym numerem GUID pomiędzy, co jest mało prawdopodobne raczej ;)

Ale argument jest poprawny.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.