我正在开发一个asp.net MVC-5 Web应用程序,基于我读过的一些文章,我不应该在Web服务器和.net web应用程序中使用并行方法。
现在在我的情况下,我有大约1,500个WebClient()
调用,我需要在foreach中发出,然后从WebClient()
调用反序列化返回的json对象。使用Parallel.Foreach
之前的原始代码如下所示,大约需要 15 分钟才能完成: -
public async Task <List<Details2>> Get()
{
try
{
using (WebClient wc = new WebClient())
{
string url = currentURL + "resources?AUTHTOKEN=" + pmtoken;
var json = await wc.DownloadStringTaskAsync(url);
resourcesinfo = JsonConvert.DeserializeObject<ResourcesInfo>(json);
}
ForEach( var c in resourcesinfo.operation.Details)
{
ResourceAccountListInfo resourceAccountListInfo = new ResourceAccountListInfo();
using (WebClient wc = new WebClient())
{
string url = currentURL + "resources/" + c.RESOURCEID + "/accounts?AUTHTOKEN=" + pmtoken;
string tempurl = url.Trim();
var json = await wc.DownloadStringTaskAsync(tempurl);
resourceAccountListInfo = JsonConvert.DeserializeObject<ResourceAccountListInfo>(json);
}
if (resourceAccountListInfo.operation.Details.CUSTOMFIELD.Count > 0)
{
List<CUSTOMFIELD> customfield = resourceAccountListInfo.operation.Details.CUSTOMFIELD.Where(a =>
a.CUSTOMFIELDLABEL.ToLower() == "name"
).ToList();
if (customfield.Count == 1)
{
PMresourcesOnly.Add(resourceAccountListInfo.operation.Details);
}
}
}//end of foreach
return PMresourcesOnly.ToList();
}
catch (Exception e)
{
}
return new List<Details2>();
}
现在我做了以下修改: -
foreach
替换为Parallel.ForEach
因为我不应该在Parallel.ForEach
中使用异步方法,所以我将DownloadStringTaskAsync
内的DownloadString
变为Parallel.Foreach
: -
public async Task <List<Details2>> Get()
{
try
{
using (WebClient wc = new WebClient())
{
string url = currentURL + "resources?AUTHTOKEN=" + pmtoken;
var json = await wc.DownloadStringTaskAsync(url);
resourcesinfo = JsonConvert.DeserializeObject<ResourcesInfo>(json);
}
Parallel.ForEach(resourcesinfo.operation.Details, new ParallelOptions { MaxDegreeOfParallelism = 7 }, (c) =>
{
ResourceAccountListInfo resourceAccountListInfo = new ResourceAccountListInfo();
using (WebClient wc = new WebClient())
{
string url = currentURL + "resources/" + c.RESOURCEID + "/accounts?AUTHTOKEN=" + pmtoken;
string tempurl = url.Trim();
var json = wc.DownloadString(tempurl);
resourceAccountListInfo = JsonConvert.DeserializeObject<ResourceAccountListInfo>(json);
}
if (resourceAccountListInfo.operation.Details.CUSTOMFIELD.Count > 0)
{
List<CUSTOMFIELD> customfield = resourceAccountListInfo.operation.Details.CUSTOMFIELD.Where(a =>
a.CUSTOMFIELDLABEL.ToLower() == "name"
).ToList();
if (customfield.Count == 1)
{
PMresourcesOnly.Add(resourceAccountListInfo.operation.Details);
}
}
});//end of foreach
return PMresourcesOnly.ToList();
}
catch (Exception e)
{
}
return new List<Details2>();
}
现在当我使用Parallel.Foreach
时,执行时间从 15 分钟减少到 7 分钟左右。但如果我的第二种方法有效,我会有点困惑,所以任何人都可以对这些问题(或任何问题)进行思考: -
正在使用Parallel.Foreach
并Webclient()
采用有效方法吗?或者我应该避免在.net和Web应用程序中使用Parallel方法吗?
使用Parallel.Foreach
时,我是否会遇到任何问题,例如return PMresourcesOnly.ToList();
会返回客户端,而仍有一些wc.DownloadString(tempurl);
未完成?
如果我想比较两种方法(Parallel.Foreach&amp; Foreach),结果是否相同?
在一些在线文章中,他们使用Task.Factory.StartNew(()
而不是Parallel.foreach
,那么它们之间的主要区别是什么?
修改
我尝试将SemaphoreSlim
定义如下: -
public async Task <List<Details2>> Get()
{
SemaphoreSlim throttler = new SemaphoreSlim(initialCount: 15);
try
{
//code goes here
var tasks = resourcesinfo.operation.Details.Select(c => TryDownloadResourceAsync(c.RESOURCEID,throttler)).ToList();
}
/// ---
private async Task<Details2> TryDownloadResourceAsync(string resourceId, SemaphoreSlim throttler)
{
await throttler.WaitAsync();
try
{
using (WebClient wc = new WebClient()) //get the tag , to check if there is a server with the same name & tag..
{}
}
finally
{
throttler.Release();
}
答案 0 :(得分:7)
正在使用Parallel.Foreach和Webclient()一个有效的方法吗?或者我应该避免在.net和Web应用程序中使用Parallel方法吗?
不,你绝对应该避免在ASP.NET应用程序中使用并行方法。
在一些在线文章中,他们使用Task.Factory.StartNew(()而不是使用Parallel.foreach,那么它们之间的主要区别是什么?
Parallel
用于data parallism(在数据项集合上运行相同的CPU绑定代码)。 StartNew
用于dynamic task parallelism(在处理项目的集合上运行相同或不同的CPU绑定代码)。
这两种方法都不合适,因为你需要做的工作是I / O绑定,而不是CPU绑定。
你真正想要的是并发(一次做多件事),而不是 parallelism 。而不是使用并行并发(通过使用多个线程一次做多个事情),你想要的是异步并发(使用无线程一次做多件事)。
通过await Task.WhenAll
在代码中可以实现异步并发:
private async Task<string> TryDownloadResourceAsync(string resourceId)
{
ResourceAccountListInfo resourceAccountListInfo = new ResourceAccountListInfo();
using (WebClient wc = new WebClient())
{
string url = currentURL + "resources/" + resourceId + "/accounts?AUTHTOKEN=" + pmtoken;
string tempurl = url.Trim();
var json = await wc.DownloadStringTaskAsync(tempurl);
resourceAccountListInfo = JsonConvert.DeserializeObject<ResourceAccountListInfo>(json);
}
if (resourceAccountListInfo.operation.Details.CUSTOMFIELD.Count > 0)
{
List<CUSTOMFIELD> customfield = resourceAccountListInfo.operation.Details.CUSTOMFIELD.Where(a =>
a.CUSTOMFIELDLABEL.ToLower() == "name"
).ToList();
if (customfield.Count == 1)
{
return resourceAccountListInfo.operation.Details;
}
}
return null;
}
public async Task <List<Details2>> Get()
{
try
{
using (WebClient wc = new WebClient())
{
string url = currentURL + "resources?AUTHTOKEN=" + pmtoken;
var json = await wc.DownloadStringTaskAsync(url);
resourcesinfo = JsonConvert.DeserializeObject<ResourcesInfo>(json);
}
var tasks = resourcesinfo.operation.Details.Select(c => TryDownloadResourceAsync(c.RESOURCEID)).ToList();
var results = await Task.WhenAll(tasks).Select(x => x != null);
return results.ToList();
}
catch (Exception e)
{
}
return new List<Details2>(); // Please, please don't do this in production.
}
作为最后一点,您可能需要查看专为异步操作而设计的HttpClient
,并且具有良好的属性,您只需要一个来实现任意数量的同步调用
答案 1 :(得分:0)
看看:
object syncObj = new object();
锁(syncObj)强>
try
{
using (WebClient wc = new WebClient())
{
string url = currentURL + "resources?AUTHTOKEN=" + pmtoken;
var json = await wc.DownloadStringTaskAsync(url);
resourcesinfo = JsonConvert.DeserializeObject<ResourcesInfo>(json);
}
object syncObj = new object(); // create sync object
Parallel.ForEach(resourcesinfo.operation.Details, new ParallelOptions { MaxDegreeOfParallelism = 7 }, (c) =>
{
ResourceAccountListInfo resourceAccountListInfo = new ResourceAccountListInfo();
using (WebClient wc = new WebClient())
{
string url = currentURL + "resources/" + c.RESOURCEID + "/accounts?AUTHTOKEN=" + pmtoken;
string tempurl = url.Trim();
var json = wc.DownloadString(tempurl);
resourceAccountListInfo = JsonConvert.DeserializeObject<ResourceAccountListInfo>(json);
}
lock(syncObj) // lock using sync object
{
PMresourcesOnly.Add(resourceAccountListInfo.operation.Details);
}
});//end of foreach
return PMresourcesOnly.ToList();
}
catch (Exception e)
{
}