如何在代码中使用Task.Run以获得适当的可伸缩性和性能?

时间:2019-06-17 09:00:39

标签: c# wpf mvvm async-await task-parallel-library

我开始对自己的代码产生极大的疑问,并且需要更多有经验的程序员的一些建议。

在我的应用程序中,单击按钮,该应用程序运行一个命令,该命令正在调用ScrapJockeys方法:

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

ScrapJockeys触发for循环,在20K至150K次之间重复代码块(取决于大小写)。在循环内部,我需要调用一个服务方法,该方法的执行需要很多时间。另外,我希望能够取消循环以及循环/方法内部正在进行的所有操作。

现在,我使用的是带有任务列表的方法,并且在循环内部触发了Task.Run。在每个任务中,我正在调用一个等待的服务方法,与同步代码相比,该方法将所有内容的执行时间减少到1/4。另外,每个任务都分配了取消令牌,例如示例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
    }
}

关于我的问题,我的代码是否一切正常?根据{{​​3}}:

  

许多异步新手通过尝试处理异步任务开始   与并行(TPL)任务相同,这是一个重大失误。

那我该怎么用?

根据this article

  

在繁忙的服务器上,这种实现可能会破坏可伸缩性。

那我应该怎么做?

请注意,服务接口方法签名为Task<LoadedJockey> ScrapSingleJockeyPlAsync(int index);

而且我也不100%肯定我在自己的服务类别中正确使用了Task.Run。内部方法将代码包装在await Task.Run(() =>内部,例如示例this article

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

    });

    return jockey;
}

据我从文章中了解到,这是一种反模式。但是我有点困惑。基于GitHub link,应该没问题...?如果没有,该如何更换?

2 个答案:

答案 0 :(得分:3)

在UI端,当您有足够长的CPU绑定代码需要将其从UI线程中移出时,应该使用Task.Run。这与服务器端完全不同,在服务器端完全使用Task.Run 是一种反模式。

就您而言,您的所有代码似乎都是基于I / O的,因此我完全不需要Task.Run

您的问题中有一条陈述与所提供的代码冲突:

  

我正在调用等待的服务方法

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

传递给Task.Run的lambda不是async,因此可能无法等待服务方法。的确是it is not

更好的解决方案是异步加载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 :(得分:1)

  

据我了解,这是一种反模式。

这是一种反模式。但是,如果无法修改服务实现,则至少应能够并行执行任务。像这样:

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