并行 for 循环和 httpclient 死锁并引发异常

Posted

技术标签:

【中文标题】并行 for 循环和 httpclient 死锁并引发异常【英文标题】:parallel for loop and httpclient deadlocks and throws exception 【发布时间】:2017-02-05 16:58:31 【问题描述】:

我很困惑为什么我的 parallels.for 循环在 httpclient 调用中不断地爆炸。 该代码适用于大约 10-15 个请求,然后会挂起很长时间,并出现 System.AggregateException 错误 我尝试了多种不同的变体,包括 webclient。 请考虑以下几点:

class Program

    static void Main(string[] args)
    
        Parallel.For(0, 250, i =>
        

            var task = GetPages.CallHttp();
            task.Wait();
            var content = task.Result;
            TestObject obj = content.ToTestObject(); //take the string and find some values in it. 
            Console.WriteLine(obj.H1Tag);
        );
    


public static class GetPages

    private static readonly HttpClient client = new HttpClient() Timeout = new TimeSpan(0,5,0);

   public static async Task<string> CallHttp()
    

        client.DefaultRequestHeaders.UserAgent.ParseAdd("customAgent/1.0");
        string astr = await client.GetStringAsync("the url I am testing").ConfigureAwait(false);
        return astr;
    


public static class StringExtensions

    private static readonly object objLock = new object();
    public static TestObject ToTestObject(this string content)
    
        lock (objLock)
        
            var obj = new TestObject();
// creates a bunch of properties inspecting the html string
            var result = new HtmlExtractions(content); 
            obj.PageTitle = result.PageTitle;
            obj.H1Tag = result.H1Tag;
           ...
            return obj;
        
    




public class HtmlExtractions


    internal HtmlDocument doc;

    public HtmlExtractions(string contentToRead)
    
        doc = new HtmlDocument();
        doc.LoadHtml(contentToRead);
    

    public string PageTitle => doc.DocumentNode.Descendants("title").FirstOrDefault()?.InnerHtml.Replace("&amp;", "&").Trim();
...

结果是抛出以下异常。

    System.AggregateException was unhandled by user code
  HResult=-2146233088
  Message=One or more errors occurred.
Source=mscorlib
StackTrace:
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at ConsoleApplication1.Program.<>c.<Main>b__0_0(Int32 i) in c:\users\username\documents\visual studio 2015\Projects\ConsoleApplication1\Program.cs:line 27
   at System.Threading.Tasks.Parallel.<>c__DisplayClass17_0`1.<ForWorker>b__1()
InnerException: 
   HResult=-2146233029
   Message=A task was canceled.
   InnerException: Id = 50, Status = Canceled, Method = "null", Result = "Not yet computed"

******按照 Todd 的建议更新 // 代码中的 cmets。太令人沮丧了。即使扩展到 5 个请求也会导致挂起。 ******

    static async void RunPagesAsync()
    
        Console.WriteLine("getting contents");
        var tasks = Enumerable.Range(0, 5).Select(i => GetPages.CallHttp());
        var contents = await Task.WhenAll(tasks);

        Console.WriteLine("Got Contents..continuing");
        foreach (var content in contents)
        
            TestObject obj = content.ToTestObject(); //take the string and find some values in it. 
            Console.WriteLine(obj.H1Tag);
        
        Console.WriteLine("completed");
    

    static void Main(string[] args)
    

      //Task.Run(() => RunPagesAsync()); //doesn't work. 
      //  RunPagesAsync(); //just hangs after what looks like 2 itterations

      //  var tasks = Enumerable.Range(0, 5).Select(i => GetPages.CallHttp());
      //  var contents = await Task.WhenAll(tasks); //won't compile under synch Main dues to await 
      //  foreach (var content in contents)
      //  
      //      TestObject obj = content.ToTestObject(); //take the string and find some values in it. 
      //      Console.WriteLine(obj.H1Tag);
      //  

        var tasks1 = Enumerable.Range(0, 5).Select(i => GetPages.CallHttp());
        var contents1 =  Task.WhenAll(tasks1); 
        contents1.Wait();
        foreach (var content in contents1.Result)
        
            TestObject obj = content.ToTestObject(); //take the string and find some values in it. 
            Console.WriteLine(obj.H1Tag);
        

【问题讨论】:

查看System.AggregateExceptionInnerExceptions 属性。此外,不需要lock 语句。据我所知,没有并发读取或写入 内部异常在帖子底部。我应该更清楚。一个任务被取消了。 InnerException:Id = 50,状态 = 已取消,方法 =“null”,结果 =“尚未计算” 关于锁我同意,但此时是在黑暗中射箭。 @PeterBons 知道可能是什么问题吗?我在没有锁的情况下尝试了相同的代码并且遇到了同样的问题。 client.DefaultRequestHeaders.UserAgent.ParseAdd("customAgent/1.0"); 行执行了 250 次,在我的测试中这会产生异常。您应该重构它,使其只执行一次,而不是CallHttp 的一部分。我想这个集合不是线程安全的。 【参考方案1】:

异步任务(释放线程)和Parallel.For(强制使用多个线程)往往不能很好地混合。 .Wait().Result 在与异步任务一起使用时都会阻塞调用并引发死锁。尝试使用 Task.WhenAll 重写您的 Main 方法并避免阻塞调用:

var tasks = Enumerable.Range(0, 250).Select(i => GetPages.CallHttp());
var contents = await Task.WhenAll(tasks);
foreach (var content in contents)

    TestObject obj = content.ToTestObject(); //take the string and find some values in it. 
    Console.WriteLine(obj.H1Tag);

【讨论】:

没有任何类型的真正运气。更新了底部有问题的代码。不过谢谢你的建议。先创建集合然后迭代是有意义的。

以上是关于并行 for 循环和 httpclient 死锁并引发异常的主要内容,如果未能解决你的问题,请参考以下文章

如何在异步函数中并行化 for 循环并跟踪 for 循环执行状态?

在 OpenMP 中并行化嵌套循环并使用更多线程执行内部循环

在 for 循环打字稿和休眠死锁中调用异步函数

锁定周期引起的死锁?不可能的事件导致它或只是一个虚假的直觉

是否可以在同一存储过程中并行处理for循环?

.Net 中的字典是不是可能在并行读取和写入时导致死锁?