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

Oczekiwanie na wykonanie wielu wątków asynchronicznych w pętli
K8
  • Rejestracja:ponad 3 lata
  • Ostatnio:6 miesięcy
  • 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
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Laska, z Polski
  • Postów:10059
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.

edytowany 2x, ostatnio: Riddle
K8
  • Rejestracja:ponad 3 lata
  • Ostatnio:6 miesięcy
  • 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
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Laska, z Polski
  • Postów:10059
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:ponad 3 lata
  • Ostatnio:6 miesięcy
  • 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
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Laska, z Polski
  • Postów:10059
0

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

SP
SP
  • Rejestracja:prawie 3 lata
  • Ostatnio:ponad 2 lata
  • 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

edytowany 1x, ostatnio: spaghetticoder
FG
  • Rejestracja:około 5 lat
  • Ostatnio:29 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 :)

edytowany 1x, ostatnio: Riddle
Riddle
Umieszczaj kod w znaczniki formatujące składnie.
K8
  • Rejestracja:ponad 3 lata
  • Ostatnio:6 miesięcy
  • 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
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Laska, z Polski
  • Postów:10059
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.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)