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ć?