问题 catch没有捕获多线程错误


以下是一个完整的控制台程序,它重现了我遇到的一个奇怪的错误。该程序读取包含远程文件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 memory 要么 Unable to read data from the transport connection: A blocking operation was interrupted by a call to WSACancelBlockingCall.

它似乎是一个原生错误,因为内部和外部捕获都没有捕获它。我从来没有见过 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

6019
2018-03-23 22:09


起源

你的外部捕获不会捕获线程中的东西,并且线程中的一堆东西不在catch中 - Keith Nicholas
尝试抓住你的整个循环 - Keith Nicholas
您是否需要访问服务器上的lavasoft广告感知程序,并从广告软件扫描中排除您的可执行文件。 - Sql Surfer
@SqlSurfer它不是Lavasoft - 请看我附加到问题的内容。 - wezten
@KeithNicholas我只是试着移动 try 高出4行,但它是一样的。 - wezten


答案:


你的例外没有抛出,因为你好,不要试图得到它。 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.Wait 要么 Task<TResult>.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,因为你只需要一个结果字符串。此类旨在用于多线程代码,因此它的方法是线程安全的。

另外,你应该提供一个 TaskCreationOptions.LongRunning 你的旗帜 StartNew 方法 (这很危险顺便说一句,但你仍然需要它):

指定任务将是一个长时间运行的粗粒度操作,涉及比细粒度系统更少,更大的组件。它提供了一个提示 TaskScheduler 超额认购可能是有保证的。

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


6
2018-03-24 05:25



谢谢,但是我只是尝试了TaskScheduler.UnobservedTaskException,并没有抓住它。我看不到HttpClient的优势,而且创建一个字符串是一种耻辱,当前我将结果直接传输到文件。我应该使用TaskCreationOptions.LongRunning(尽管它没有解决问题)。 - wezten
这也不能解释为什么内部捕获物不会捕获它。 - wezten
@wezten你的错误是关于基础设施的,所以它可能发生在一些超出范围的系统代码中 try。 HttpClient 也适用于流,你没有阅读文档: tugberkugurlu.com/archive/... - VMAtm
@wezten另外,你检查了故障的任务吗?您的例外必须是开启的 AppDomain.UnhandledException 要么 TaskScheduler.UnobservedTaskException 或者在 task.Exception 如果是 IsFaulted。如果您找不到此例外,则表示它确实不在您的应用中。 - VMAtm
我无法检查故障任务,因为它永远不会通过WaitAll。 - wezten


毕竟问题出在Lavasoft Web Companion上。虽然我已经禁用了它,但仍然有一些东西在后台运行。卸载它,修复了问题。


4
2018-04-20 20:15





您可以在任务中添加续集。

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

0
2018-03-25 20:47





您的示例中的代码实际上不受try / catch的保护。 一个错误会在线程上抛出未捕获的异常。

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

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.");
    }
}

0
2018-04-25 21:09



我试过了 - 请看第五条关于这个问题的评论。 - wezten
@wezten - 哦!嗯,那令人尴尬。 - FastAl