【问题标题】:Multithread error not caught by catch多线程错误未被 catch 捕获
【发布时间】:2017-03-23 22:09:58
【问题描述】:

以下是一个完整的控制台程序,它重现了我遇到的一个奇怪错误。该程序读取一个包含远程文件 url 的文件,每行一个。它会启动 50 个线程以将它们全部下载。

static void Main(string[] args)
{
    try
    {
        string filePath = ConfigurationManager.AppSettings["filePath"],
            folder = ConfigurationManager.AppSettings["folder"];
        Directory.CreateDirectory(folder);
        List<string> urls = File.ReadAllLines(filePath).Take(10000).ToList();

        int urlIX = -1;
        Task.WaitAll(Enumerable.Range(0, 50).Select(x => Task.Factory.StartNew(() =>
          {
              while (true)
              {
                  int curUrlIX = Interlocked.Increment(ref urlIX);
                  if (curUrlIX >= urls.Count)
                      break;
                  string url = urls[curUrlIX];
                  try
                  {
                      var req = (HttpWebRequest)WebRequest.Create(url);
                      using (var res = (HttpWebResponse)req.GetResponse())
                      using (var resStream = res.GetResponseStream())
                      using (var fileStream = File.Create(Path.Combine(folder, Guid.NewGuid() + url.Substring(url.LastIndexOf('.')))))
                          resStream.CopyTo(fileStream);
                  }
                  catch (Exception ex)
                  {
                      Console.WriteLine("Error downloading img: " + url + "\n" + ex);
                      continue;
                  }
              }
          })).ToArray());
    }
    catch
    {
        Console.WriteLine("Something bad happened.");
    }
}

在我的本地计算机上它工作正常。在服务器上,下载了几百张图片后,显示Attempted to read or write protected memoryUnable to read data from the transport connection: A blocking operation was interrupted by a call to WSACancelBlockingCall. 的错误。

这似乎是一个原生错误,因为内部和外部的 catch 都没有捕捉到它。我从没见过Something bad happened.

我在WinDbg 中运行它,它显示如下:

(3200.1790): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
LavasoftTcpService64+0x765f:
00000001`8000765f 807a1900        cmp     byte ptr [rdx+19h],0 ds:baadf00d`0000001a=??
0:006> g
(3200.326c): CLR exception - code e0434352 (first chance)
(3200.326c): CLR exception - code e0434352 (first chance)
(3200.2b9c): Access violation - code c0000005 (!!! second chance !!!)
LavasoftTcpService64!WSPStartup+0x9749:
00000001`8002c8b9 f3a4            rep movs byte ptr [rdi],byte ptr [rsi]

我刚刚关闭了 Lavasoft,现在 WinDbg 显示如下:

Critical error detected c0000374
(3c4.3494): Break instruction exception - code 80000003 (first chance)
ntdll!RtlReportCriticalFailure+0x4b:
00007fff`4acf1b2f cc              int     3
0:006> g
(3c4.3494): Unknown exception - code c0000374 (first chance)
(3c4.3494): Unknown exception - code c0000374 (!!! second chance !!!)
ntdll!RtlReportCriticalFailure+0x8c:
00007fff`4acf1b70 eb00            jmp     ntdll!RtlReportCriticalFailure+0x8e (00007fff`4acf1b72)
0:006> g
WARNING: Continuing a non-continuable exception
(3c4.3494): C++ EH exception - code e06d7363 (first chance)
HEAP[VIPJobsTest.exe]: HEAP: Free Heap block 0000007AB96CC5D0 modified at 0000007AB96CC748 after it was freed
(3c4.3494): Break instruction exception - code 80000003 (first chance)
ntdll!RtlpBreakPointHeap+0x1d:
00007fff`4acf3991 cc              int     3

【问题讨论】:

  • 你的外部catch不会捕捉到线程中的东西,线程中有一堆东西不在catch内
  • 尝试捕获整个 while 循环
  • 您是否需要转到服务器上的 lavasoft 广告感知程序,并从广告软件扫描中排除您的可执行文件。
  • @SqlSurfer 这不是 Lavasoft - 请查看我附加到问题的内容。
  • @KeithNicholas 我刚刚尝试将try 移高 4 行,但还是一样。

标签: c# multithreading task-parallel-library


【解决方案1】:

您的异常不会抛出,因为您不会尝试获取它。 WaitAll 方法基本上是一个Barrier,它等待(哈哈)所有任务完成。它是void,因此您必须为您的任务保存一个参考,以便进一步操作,如下所示:

var tasks = Enumerable.Range(0, 50).Select(x => Task.Factory.StartNew(() =>
{
    while (true)
    {
        // ..
        try
        {
            // ..
        }
        catch (Exception ex)
        {
            // ..
        }
    }
})).ToArray();

Task.WaitAl((tasks);

// investigate exceptions here
var faulted = tasks.Where(t => t.IsFaulted);

根据MSDN,当您使用静态或实例Task.WaitTask&lt;TResult&gt;.Wait 方法或.Result 属性之一时会传播异常。但是,这不是您的选择,因为您在此处使用 try/catch。所以需要订阅TaskScheduler.UnobservedTaskException事件:

TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
    Console.WriteLine("Error." + e);
    e.SetObserved();
}

为什么不扔就跑了?

此应用程序域范围的事件提供了一种机制来防止异常升级策略(默认情况下终止进程)触发。

为了让开发人员更轻松地编写基于任务的异步代码,.NET Framework 4.5 更改了未观察到的异常的默认异常行为。尽管未观察到的异常仍会引发UnobservedTaskException 异常,但默认情况下进程不会终止。相反,在引发事件后由运行时处理异常,无论事件处理程序是否观察到异常。可以配置此行为。从.NET Framework 4.5 开始,您可以使用配置元素恢复到 .NET Framework 4 的行为并终止进程:

<configuration> 
 <runtime> 
  <ThrowUnobservedTaskExceptions enabled="true"/> 
 </runtime> 
</configuration>

现在,回到您的代码。考虑使用静态HttpClient 实例而不是HttpWebRequest,因为您只需要一个结果字符串。这个类被设计用于多线程代码,所以它的方法是线程安全的。

此外,您应该为您的 StartNew 方法提供一个 TaskCreationOptions.LongRunning 标志(顺便说一下,which is dangerous,但您仍然需要它):

指定一个任务将是一个长时间运行的粗粒度操作,比细粒度系统涉及更少、更大的组件。它向TaskScheduler 提供了一个提示,表明可能需要超额订阅。

超额订阅允许您创建比可用硬件线程数更多的线程。它还向任务调度程序提供了一个提示,即该任务可能需要一个额外的线程,这样它就不会阻塞其他线程或本地线程池队列上的工作项的前进。

【讨论】:

  • 谢谢,但是我刚刚尝试了 TaskScheduler.UnobservedTaskException,但它没有捕获它。我看不到 HttpClient 的优势,而且当我目前将结果直接流式传输到文件时,创建一个字符串是一种耻辱。我应该使用 TaskCreationOptions.LongRunning (虽然它不能解决问题)。
  • 这也不能解释为什么内部的catch没有抓住它。
  • @wezten 您的错误与基础设施有关,因此它可能发生在超出try 范围的某些系统代码中。 HttpClient 也适用于流,您没有阅读文档:tugberkugurlu.com/archive/…
  • @wezten 另外,您是否检查了故障任务?您的异常必须在AppDomain.UnhandledExceptionTaskScheduler.UnobservedTaskException 上,如果是IsFaulted,则在task.Exception 上。如果您找不到此异常,则表示它确实不在您的应用中发生。
  • 我无法检查有问题的任务,因为它永远不会超过 WaitAll。
【解决方案2】:

毕竟问题出在 Lavasoft Web Companion 上。即使我禁用了它,它仍然有一些东西在后台运行。卸载它,解决了这个问题。

【讨论】:

    【解决方案3】:

    你可以在你的任务上添加一个延续。

     Task.Factory.StartNew(() => ...)
        .ContinueWith (task => 
        {
           If (task.isFaulted)
           {
               //task.Exception
               //handle the exception from this context
           }
        });
    

    【讨论】:

      【解决方案4】:

      您的示例中有代码实际上不受 try/catch 保护。 那里的错误会在线程上引发未捕获的异常。

      这是一个重构(cmets 由以前未受保护的代码)。外部尝试不会捕获这些,因为它在起始线程上。因此它将在子线程上未处理

      static void Main(string[] args)
      {
          try
          {
              string filePath = ConfigurationManager.AppSettings["filePath"],
                  folder = ConfigurationManager.AppSettings["folder"];
              Directory.CreateDirectory(folder);
              List<string> urls = File.ReadAllLines(filePath).Take(10000).ToList();
      
              int urlIX = -1;
              Task.WaitAll(Enumerable.Range(0, 50).Select(x => Task.Factory.StartNew(() =>
                {
                    try
                    {
                        while (true) // ** was unprotected
                        {
                            int curUrlIX = Interlocked.Increment(ref urlIX);  // ** was unprotected
                            if (curUrlIX >= urls.Count)   // ** was unprotected
                                break;                    // ** was unprotected
                            string url = urls[curUrlIX];  // ** was unprotected
                            try
                            {
                                var req = (HttpWebRequest)WebRequest.Create(url);
                                using (var res = (HttpWebResponse)req.GetResponse())
                                using (var resStream = res.GetResponseStream())
                                using (var fileStream = File.Create(Path.Combine                    (folder, Guid.NewGuid() + url.Substring(url.LastIndexOf('.')))))
                                    resStream.CopyTo(fileStream);
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine("Error downloading img: " + url + "\n" + ex);
                                continue;
                            }
                        } // while
                    } // try
                })).ToArray());
          }
          catch
          {
              Console.WriteLine("Something bad happened.");
          }
      }
      

      【讨论】:

      • 我已经尝试过了 - 请参阅关于该问题的第五条评论。
      • @wezten - 哦!嗯,这很尴尬。
      猜你喜欢
      • 2014-03-04
      • 2019-05-19
      • 2012-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多