在我的asp.net mvc Web应用程序中使用带有WebClient()的Parallel.Foreach有任何缺点或风险吗?

joh*_* Gu 2 .net c# asp.net parallel-processing asp.net-mvc

我正在开发一个asp.net MVC-5 Web应用程序,基于我读过的一些文章,我不应该在Web服务器和.net web应用程序中使用并行方法.现在在我的情况下WebClient(),我需要在foreach中发出大约1,500个调用,然后从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>();
            }
Run Code Online (Sandbox Code Playgroud)

现在我做了以下修改: -

  • 我代替foreachParallel.ForEach
  • 因为我不应该使用内异步方法Parallel.ForEach,所以我chnage的DownloadStringTaskAsyncDownloadString里面的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>();
            }
    
    Run Code Online (Sandbox Code Playgroud)

现在当我使用时,Parallel.Foreach执行时间从15分钟减少到大约7分钟.但如果我的第二种方法有效,我会有点困惑,所以任何人都可以对这些问题(或任何问题)进行思考: -

  1. 是使用Parallel.Foreach带有Webclient()一种有效的方法可循?或者我应该避免在.net和Web应用程序中使用Parallel方法?

  2. 在使用时Parallel.Foreach我是否可以面对任何问题,例如return PMresourcesOnly.ToList();返回客户端,而仍有一些wc.DownloadString(tempurl);没有完成?

  3. 如果我想比较2种方法(Parallel.Foreach&Foreach),结果是否相同?

  4. 在他们使用的一些在线文章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();
}
Run Code Online (Sandbox Code Playgroud)

/// ---

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();
            }
Run Code Online (Sandbox Code Playgroud)

Ste*_*ary 7

正在使用Parallel.Foreach和Webclient()一个有效的方法来遵循?或者我应该避免在.net和Web应用程序中使用Parallel方法?

不,你绝对应该避免在ASP.NET应用程序中使用并行方法.

在一些在线文章中,他们使用Task.Factory.StartNew(()而不是使用Parallel.foreach,那么它们之间的主要区别是什么?

Parallel用于数据并行(在数据项集合上运行相同的CPU绑定代码).StartNew用于动态任务并行(在处理它时更改的项集合上运行相同或不同的CPU绑定代码).

这两种方法都不合适,因为您需要做的工作是I/O绑定,而不是CPU绑定.

你真正想要的是并发性(一次做多件事),而不是并行性.而不是使用并行并发(通过使用多个线程一次做多个事情),你想要的是异步并发(使用无线程一次做多件事).

代码中的异步并发是可能的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.
}
Run Code Online (Sandbox Code Playgroud)

最后要注意的是,您可能需要研究一下HttpClient,这是为异步操作而设计的,并且具有良好的属性,您只需要其中一个用于任意数量的同时调用.

  • @johnG:1)限制异步并发的一种方法是使用`SemaphoreSlim`;SO上有几个答案说明了如何做。2)异步任务不会“执行”,因此它们[不会耗尽线程](http://blog.stephencleary.com/2013/11/there-is-no-thread.html)。3)不是真的;TPL数据流为您提供了一个网格,您可以将其用作生产者/消费者队列。异步并发将它们全部启动,然后(异步)等待它们全部完成,因此没有排队。您可以进行节流,如果您倾斜头部并斜视一下,它看起来就像一个队列。 (2认同)
  • @johnG:4)任务的数量无关紧要;它们占用很少的资源(一点内存,就是这样)。这是因为任务不会“执行”。您可以限制(参见 (1)),这将允许您限制同时发出的 *web 请求 * 的数量(对于 1500 个请求,服务器可能会在其中一些请求上超时)。但是您不会限制任务数量,因为这样做没有任何好处。最后一点,您可能对 [我的书](http://stephencleary.com/book/) 感兴趣,其中涵盖了并行、数据流和异步,包括对每个的限制。 (2认同)