Answered by:
Proper error handling with Tasks

Question
-
Hi all, I've attempted to create a Windows Service that uses an array of FileSystemWatchers to watch several ftp directories for incoming files. A file is 'dropped' in the ftp directory, then in the Created event of the FileSystemWatcher, a task is created to handle the file processing. So each time a file is created in an ftp directory, a task is created to handle the processing operations for that file. The code looks like this:
Public Sub Begin() Handles Me.Load 'List of ftp directories to monitor 'This will probably come from a database 'For now it's a list of files on my hard drive Dim strDirNames(9) As String For i As Int32 = 0 To 9 strDirNames(i) = "C:\Users\choffmaster\Documents\UACTestFolder\Test" & i Next ' ' 'Create an array of FileSystemWatchers Dim strFSW(9) As FileSystemWatcher Try For y As Int32 = 0 To strDirNames.Count - 1 strFSW(y) = New FileSystemWatcher strFSW(y).Path = strDirNames(y) strFSW(y).Filter = "*.dat" ' ' 'Subscribe to the created event AddHandler strFSW(y).Created, AddressOf processFileWatcher strFSW(y).EnableRaisingEvents = True ' Next Catch ex As Exception End Try End Sub Private Sub processFileWatcher(source As Object, e As FileSystemEventArgs) 'Start threads to handle file processing Dim tTask = Task.Factory.StartNew(Sub() FileProcessor(e.FullPath)) End Sub
In the FileProcessor sub, various operations are formatting the incoming ftp file for other operations (that are not included in my code sample, because they're not written yet). Everything seems to be working as expected if I manually drop a file into a directory (one at a time, obviously). But in reality, files will be coming in more frequently than that, and then after formatting, other actions including connecting to a remote service will happen.
I'm concerned about error handling with Tasks. I am relatively new to tasks. I understand that exceptions are propagated back to the calling thread as an aggregate, and that the list of inner exceptions can be enumerated to be handled properly. I have visited this link: http://msdn.microsoft.com/en-us/library/dd997415.aspx#Y3536 , but I don't understand how to set up exception handling for my service. It seems like I will also need a Task.Wait() statement, because that statement will allow exceptions to be propagated. So that statement is easy enough to add.
Can anybody give me a little more guidance on what to do after that? Thanks, Carrie
Monday, May 7, 2012 6:27 PM
Answers
-
Handling exceptions that bubble out of tasks can be tricky. If you fail to observe an unhandled exception from a task, i.e wait on the task or the attempt to obtain the result from the task then the exception will be rethrown on the finaliser thread after the task has been deemed to be garbage. If it does get re-thrown on the finalizer thread your service will terminate immediately. This is ultimately what needs to happen if your application throws an unhandled exception, as by failing to handle it you are saying I wasn't expecting it, and as such I can't guarantee that the process is in a state that is safe to proceed. In other words don't limp on and hope.
Relying on finalisers is a problem, in that between the exception and the finaliser running lots of other code could have run against a corrupt state, so really you want to handle the error, and perhaps restart as soon as possible. Therefore I would suggest inside the root method of your task you have a try/catch block, that catches any exceptions that are recoverable, for example possibly IOExceptions, log the fact that an error has happened and continue processing other files perhaps. But always have a final catch all catch block that will log the fact that something unexpected happened, and then shutdown the process as soon as possible, and perhaps restart it.
Andrew Clymer www.rocksolidknowledge.com
- Proposed as answer by Stephen Toub - MSFTMicrosoft employee, Moderator Monday, June 25, 2012 12:59 AM
- Marked as answer by Stephen Toub - MSFTMicrosoft employee, Moderator Friday, June 29, 2012 10:29 PM
Tuesday, May 8, 2012 7:13 PM -
If a Task is being waited upon then an UnhandledException gets thrown:
i.e.
Task task = new Task(MyWork); task.Start(); if(!task.Wait(10)) //gets thrown here { } public static MyWork() { throw new Exception(); }
If it's not waited upon then it gets thrown in the finalizer for the Task which means you have no idea or control over when it's going to be thrown. You can use cancellationToken's to some extent to cancel a Task if it hasn't completed in a specified time and so won't throw an (unhandled) exception when you kill it (it will throw an OperationcancelledException).
If you're going to catch the AggregateException it can be helpful to use the Flatten() method to simplify the innerExceptions:
try { task.Wait(10, source.Token); //CancellationToken } catch (AggregateException exception) { foreach (var i in exception.Flatten().InnerExceptions) { //some action using i.Message); } }
Chris
- Proposed as answer by Stephen Toub - MSFTMicrosoft employee, Moderator Monday, June 25, 2012 1:00 AM
- Marked as answer by Stephen Toub - MSFTMicrosoft employee, Moderator Friday, June 29, 2012 10:29 PM
Tuesday, June 12, 2012 5:00 PM
All replies
-
Handling exceptions that bubble out of tasks can be tricky. If you fail to observe an unhandled exception from a task, i.e wait on the task or the attempt to obtain the result from the task then the exception will be rethrown on the finaliser thread after the task has been deemed to be garbage. If it does get re-thrown on the finalizer thread your service will terminate immediately. This is ultimately what needs to happen if your application throws an unhandled exception, as by failing to handle it you are saying I wasn't expecting it, and as such I can't guarantee that the process is in a state that is safe to proceed. In other words don't limp on and hope.
Relying on finalisers is a problem, in that between the exception and the finaliser running lots of other code could have run against a corrupt state, so really you want to handle the error, and perhaps restart as soon as possible. Therefore I would suggest inside the root method of your task you have a try/catch block, that catches any exceptions that are recoverable, for example possibly IOExceptions, log the fact that an error has happened and continue processing other files perhaps. But always have a final catch all catch block that will log the fact that something unexpected happened, and then shutdown the process as soon as possible, and perhaps restart it.
Andrew Clymer www.rocksolidknowledge.com
- Proposed as answer by Stephen Toub - MSFTMicrosoft employee, Moderator Monday, June 25, 2012 12:59 AM
- Marked as answer by Stephen Toub - MSFTMicrosoft employee, Moderator Friday, June 29, 2012 10:29 PM
Tuesday, May 8, 2012 7:13 PM -
If a Task is being waited upon then an UnhandledException gets thrown:
i.e.
Task task = new Task(MyWork); task.Start(); if(!task.Wait(10)) //gets thrown here { } public static MyWork() { throw new Exception(); }
If it's not waited upon then it gets thrown in the finalizer for the Task which means you have no idea or control over when it's going to be thrown. You can use cancellationToken's to some extent to cancel a Task if it hasn't completed in a specified time and so won't throw an (unhandled) exception when you kill it (it will throw an OperationcancelledException).
If you're going to catch the AggregateException it can be helpful to use the Flatten() method to simplify the innerExceptions:
try { task.Wait(10, source.Token); //CancellationToken } catch (AggregateException exception) { foreach (var i in exception.Flatten().InnerExceptions) { //some action using i.Message); } }
Chris
- Proposed as answer by Stephen Toub - MSFTMicrosoft employee, Moderator Monday, June 25, 2012 1:00 AM
- Marked as answer by Stephen Toub - MSFTMicrosoft employee, Moderator Friday, June 29, 2012 10:29 PM
Tuesday, June 12, 2012 5:00 PM