Как мне использовать Task.Run в моем коде для правильной масштабируемости и производительности? - PullRequest
0 голосов
/ 17 июня 2019

У меня появились ОГРОМНЫЕ сомнения относительно моего кода, и мне нужны советы от более опытных программистов.

В моем приложении при нажатии кнопки приложение запускает команду, которая вызывает метод ScrapJockeys:

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

ScrapJockeys запускает цикл for, повторяя кодовый блок от 20 до 150 тысяч раз (зависит от случая).Внутри цикла мне нужно вызвать метод службы, где выполнение метода занимает много времени.Кроме того, я хотел иметь возможность отмены цикла и всего, что происходит внутри цикла / метода.

Сейчас я нахожусь с методом со списком задач и внутри цикласрабатывает Task.Run.Внутри каждой задачи я вызываю ожидаемый сервисный метод, который сокращает время выполнения всего до 1/4 по сравнению с синхронным кодом.Кроме того, каждой задаче назначен токен отмены, как в примере ссылка GitHub :

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

Так что по моему вопросу все в порядке с моим кодом?Согласно этой статье :

Многие асинхронные новички начинают с попыток рассматривать асинхронные задачи так же, как параллельные (TPL) задачи, и это серьезная ошибка.

Что мне тогда использовать?

И согласно этой статье :

На загруженном сервере такая реализация может убить масштабируемость.

Итак, как мне это сделать?

Обратите внимание, что сигнатура метода интерфейса службы Task<LoadedJockey> ScrapSingleJockeyPlAsync(int index);

И я тоже неНа 100% уверен, что я правильно использую Task.Run в своем классе обслуживания.Методы внутри обертывают код внутри await Task.Run(() =>, как в примере ссылка на GitHub :

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

    });

    return jockey;
}

Насколько я понимаю из статей, это своего рода анти-шаблон.Но я немного смущен.Исходя из этого ТАКОГО ответа , все должно быть в порядке ...?Если нет, то как его заменить?

Ответы [ 2 ]

3 голосов
/ 17 июня 2019

На стороне пользовательского интерфейса вы должны использовать Task.Run, когда у вас есть код с привязкой к процессору, который достаточно длинный, чтобы его нужно было удалить из потока пользовательского интерфейса. Это полностью отличается от серверной стороны, где использование Task.Run вообще является анти-паттерном.

В вашем случае весь ваш код, похоже, основан на вводе / выводе, поэтому я не вижу необходимости в Task.Run.

В вашем вопросе есть утверждение, которое противоречит предоставленному коду:

Я вызываю ожидаемый метод обслуживания

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

Лямбда, переданная в Task.Run, не является async, поэтому метод обслуживания не может быть ожидаемым. И действительно, это не .

Лучшим решением было бы загрузить HTML асинхронно (например, используя HttpClient.GetStringAsync), а затем вызвать HtmlDocument.LoadHtml, что-то вроде этого:

public async Task<LoadedJockey> ScrapSingleJockeyPlAsync(int index)
{
  LoadedJockey jockey = new LoadedJockey();
  ...
  string link = sb.ToString();

  var html = await httpClient.GetStringAsync(link).ConfigureAwait(false);
  HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
  doc.LoadHtml(html);

  if (jockey.Name == null)
  ...

  return jockey;
}

А также удалите Task.Run из вашего for цикла:

private async Task ScrapJockey(string dataType)
{
  LoadedJockey jockey = new LoadedJockey();

  CancellationToken.ThrowIfCancellationRequested();

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

  //doing some stuff with results in here
}

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++)
  {
    tasks.Add(ScrapJockey(dataType));
  }

  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
  }
}
1 голос
/ 17 июня 2019

Насколько я понимаю из статей, это своего рода анти-паттерн.

Это анти-паттерн. Но если вы не можете изменить реализацию сервиса, вы должны хотя бы уметь выполнять задачи параллельно. Как то так:

public async Task ScrapJockeys(int startIndex, int stopIndex, string dataType)
{
    ConcurrentBag<Task> tasks = new ConcurrentBag<Task>();
    ParallelOptions parallelLoopOptions = new ParallelOptions() { CancellationToken = CancellationToken };
    Parallel.For(startIndex, stopIndex, parallelLoopOptions, i =>
    {
        int j = i;
        switch (dataType)
        {
            case "jockeysPl":
                tasks.Add(_scrapServices.ScrapSingleJockeyPlAsync(j));
                break;
            case "jockeysCz":
                tasks.Add(_scrapServices.ScrapSingleJockeyCzAsync(j));
                break;
        }
    });

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