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