我开始对自己的代码产生极大的疑问,并且需要更多有经验的程序员的一些建议。
在我的应用程序中,单击按钮,该应用程序运行一个命令,该命令正在调用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,应该没问题...?如果没有,该如何更换?
答案 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
}
}