以下是一个完整的控制台程序,可以重现我遇到的一个奇怪的错误。该程序读取包含远程文件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
答案 0 :(得分:6)
你的例外没有抛出,因为你好,不要试图得到它。 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
,因为您只需要一个结果字符串。此类旨在用于多线程代码,因此它的方法是线程安全的。
此外,您应该为StartNew
方法提供TaskCreationOptions.LongRunning
标记(which is dangerous顺便说一句,但您仍需要它):
指定任务将是一个长时间运行的粗粒度操作,涉及比细粒度系统更少,更大的组件。它提供了
TaskScheduler
的提示,即可以保证超额认购。Oversubscription允许您创建比可用硬件线程数更多的线程。它还向任务调度程序提供了一个提示,即任务可能需要一个额外的线程,这样它就不会阻止本地线程池队列中其他线程或工作项的前进。
答案 1 :(得分:4)
问题出在Lavasoft Web Companion上。虽然我已经禁用它,但仍然有一些东西在后台运行。卸载它,修复了问题。
答案 2 :(得分:0)
您可以在任务中添加续集。
Task.Factory.StartNew(() => ...)
.ContinueWith (task =>
{
If (task.isFaulted)
{
//task.Exception
//handle the exception from this context
}
});
答案 3 :(得分:0)
您的示例中的代码实际上不受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.");
}
}