c# - catch 没有捕获到多线程错误

标签 c# multithreading task-parallel-library

以下是一个完整的控制台程序,它重现了我遇到的一个奇怪的错误。该程序读取一个包含远程文件 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调用 WSACancelBlockingCall。

这似乎是一个原生错误,因为内部和外部catch 都没有捕获到它。我从未见过 发生了不好的事情。

我在 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

最佳答案

您的异常不会抛出,因为您并没有尝试获取它。 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 属性(property)。但是,这不是您的选择,因为您正在使用 try/catch这里。所以你需要订阅 TaskScheduler.UnobservedTaskException 事件:

TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

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

为什么不抛出就跑?

This application domain-wide event provides a mechanism to prevent exception escalation policy (which, by default, terminates the process) from triggering.

To make it easier for developers to write asynchronous code based on tasks, the .NET Framework 4.5 changes the default exception behavior for unobserved exceptions. Although unobserved exceptions still raise the UnobservedTaskException exception, the process does not terminate by default. Instead, the exception is handled by the runtime after the event is raised, regardless of whether an event handler observes the exception. This behavior can be configured. Starting with the .NET Framework 4.5, you can use the configuration element to revert to the behavior of the .NET Framework 4 and terminate the process:

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

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

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

Specifies that a task will be a long-running, coarse-grained operation involving fewer, larger components than fine-grained systems. It provides a hint to the TaskScheduler that oversubscription may be warranted.

Oversubscription lets you create more threads than the available number of hardware threads. It also provides a hint to the task scheduler that an additional thread might be required for the task so that it does not block the forward progress of other threads or work items on the local thread-pool queue.

关于c# - catch 没有捕获到多线程错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42987919/

相关文章:

c# - Json.NET 在没有父级的情况下序列化对象属性

ruby - LoadError : Expected {app_path}/models/model file. rb 定义模型名称

c - 它是如何工作的? pthread_cond_signal() 和 pthread_cond_wait()

oop - TPL DataFlow 和架构设计

c# - 集成 Google 网站站长工具 api

c# - 捕获 block 选择

等待超时后使用 Task.Wait 记录 C#/Tasks 错误两次

c# - 如何创建任务<>我可以手动完成

为二维数组调用 Array.GetLength(1) 时出现 C# IndexOutOfRangeException

c# - 针对 F# 的案例是什么?