Po zamieszczeniu tego wątku zacząłem mieć poważne wątpliwości odnośnie tego czy poprawnie używam Task.Run, więc ciekaw jestem szerszej opinii dotnetowców.

W mojej aplikacji po kliknięciu przycisku uruchamia się komenda która wywołuje metodę ScrapJockeys:

if (UpdateJockeysPl) await ScrapJockeys(JPlFrom, JPlTo + 1, "jockeysPl"); //1 - 1049

ScrapJockeys odpala pętlę for,z 20K - 150K powtórzeniami (w zależności od przypadku). Wewnątrz pętli muszę wywołać metodę serwisu której wykonanie zajmuje sporo czasu (i tak kilkdziesiąt tysięcy razy). Chciałem też mieć możliwość anulowania ich wykonania w dowolnym momencie.

Obecnie mam metodę z listą Task-ów, a wewnątrz bloku pętli odpalam Task.Run. Jeśli każde zapętlenie wykonuje odzzielny Task, to egzekucja całości zajmuje 1/4 czasu niż przy wykonaniu synchronicznym. W każdym Tasku wywołuję await-owaną metodę serwisu. Także każdy Task ma przypisany cancellation token, jak w przykładzie GitHub link:

public async Task ScrapJockeys(int startIndex, int stopIndex, string dataType)
        {
            //init values and controls in here
            List<Task> tasks = new List<Task>();
            for (int i = startIndex; i < stopIndex; i++)
            {
                int j = i;
                Task task = Task.Run(async () =>
                {
                    LoadedJockey jockey = new LoadedJockey();

                    CancellationToken.ThrowIfCancellationRequested();

                    if (dataType == "jockeysPl") jockey = await _scrapServices.ScrapSingleJockeyPlAsync(j);
                    if (dataType == "jockeysCz") jockey = await _scrapServices.ScrapSingleJockeyCzAsync(j);

                    //doing some stuff with results in here

                }, TokenSource.Token);

                tasks.Add(task);
            }

            try
            {
                await Task.WhenAll(tasks);
            }
            catch (OperationCanceledException)
            {
                //
            }
            finally
            {
                await _dataServices.SaveAllJockeysAsync(Jockeys.ToList()); //saves everything to JSON file

                //soing some stuff with UI props in here
            }
        }

Zastanawiam się, czy z moim kodem jest wszystko ok? Zgodnie z tym artykułem:

Many async newbies start off by trying to treat asynchronous tasks the
same as parallel (TPL) tasks and this is a major misstep.

W takim razie jak to powinienem napisać?

Zgodnie z tym artykułem:

On a busy server, this kind of implementation can kill scalability.

Jak to powinienem zrobić?

Sygnatura metody serwisu wygląda następująco: Task<LoadedJockey> ScrapSingleJockeyPlAsync(int index);

Też nie mam 100% pewności czy w klasie serwisu poprawnie wykorzystuję Task.Run. Funkcje tej klasy wrapują kod wewnątrz await Task.Run(() =>, tak jak w poniższym przykładie GitHub link:

public async Task<LoadedJockey> ScrapSingleJockeyPlAsync(int index)
        {
            LoadedJockey jockey = new LoadedJockey();
            await Task.Run(() =>
            {
                //do some time consuming things

            });
            return jockey;
        }

Jeśli dobrze rozumiem z artykułów i odpowiedzi w poście w innym dziale, to jest rodzaj anty-patternu. Ale wg tej odpowiedzi na SO wszystko powinno byś OK...? Jeśli nie, czym to zastąpić?