7

Windows service: Generating a set of FileWatcher objects from a list of directories to watch in a config file, have the following requirements:

  1. File processing can be time consuming - events must be handled on their own task threads
  2. Keep handles to the event handler tasks to wait for completion in an OnStop() event.
  3. Track the hashes of uploaded files; don't reprocess if not different
  4. Persist the file hashes to allow OnStart() to process files uploaded while the service was down.
  5. Never process a file more than once.

(Regarding #3, we do get events when there are no changes... most notably because of the duplicate-event issue with FileWatchers)

To do these things, I have two dictionaries - one for the files uploaded, and one for the tasks themselves. Both objects are static, and I need to lock them when adding/removing/updating files and tasks. Simplified code:

public sealed class TrackingFileSystemWatcher : FileSystemWatcher {

    private static readonly object fileWatcherDictionaryLock = new object();
    private static readonly object runningTaskDictionaryLock = new object();

    private readonly Dictionary<int, Task> runningTaskDictionary = new Dictionary<int, Task>(15);
    private readonly Dictionary<string, FileSystemWatcherProperties>  fileWatcherDictionary = new Dictionary<string, FileSystemWatcherProperties>();

    //  Wired up elsewhere
    private void OnChanged(object sender, FileSystemEventArgs eventArgs) {
        this.ProcessModifiedDatafeed(eventArgs);
    }

    private void ProcessModifiedDatafeed(FileSystemEventArgs eventArgs) {

        lock (TrackingFileSystemWatcher.fileWatcherDictionaryLock) {

            //  Read the file and generate hash here

            //  Properties if the file has been processed before
            //  ContainsNonNullKey is an extension method
            if (this.fileWatcherDictionary.ContainsNonNullKey(eventArgs.FullPath)) {

                try {
                    fileProperties = this.fileWatcherDictionary[eventArgs.FullPath];
                }
                catch (KeyNotFoundException keyNotFoundException) {}
                catch (ArgumentNullException argumentNullException) {}
            }
            else {  
                // Create a new properties object
            }


            fileProperties.ChangeType = eventArgs.ChangeType;
            fileProperties.FileContentsHash = md5Hash;
            fileProperties.LastEventTimestamp = DateTime.Now;

            Task task;
            try {
                task = new Task(() => new DatafeedUploadHandler().UploadDatafeed(this.legalOrg, datafeedFileData), TaskCreationOptions.LongRunning);
            }
            catch {
              ..
            }

            //  Only lock long enough to add the task to the dictionary
            lock (TrackingFileSystemWatcher.runningTaskDictionaryLock) {
                 try {
                    this.runningTaskDictionary.Add(task.Id, task);  
                }
                catch {
                  ..
                }    
            }


            try {
                task.ContinueWith(t => {
                    try {
                        lock (TrackingFileSystemWatcher.runningTaskDictionaryLock) {
                            this.runningTaskDictionary.Remove(t.Id);
                        }

                        //  Will this lock burn me?
                        lock (TrackingFileSystemWatcher.fileWatcherDictionaryLock) {
                            //  Persist the file watcher properties to
                            //  disk for recovery at OnStart()
                        }
                    }
                    catch {
                      ..
                    }
                });

                task.Start();
            }
            catch {
              ..
            }


        }

    }

}

What's the effect of requesting a lock on the FileSystemWatcher collection in the ContinueWith() delegate when the delegate is defined within a lock on the same object? I would expect it to be fine, that even if the task starts, completes, and enters the ContinueWith() before ProcessModifiedDatafeed() releases the lock, the task thread would simply be suspended until the creating thread has released the lock. But I want to make sure I'm not stepping on any delayed execution landmines.

Looking at the code, I may be able to release the lock sooner, avoiding the issue, but I'm not certain yet... need to review the full code to be sure.


UPDATE

To stem the rising "this code is terrible" comments, there are very good reasons why I catch the exceptions I do, and am catching so many of them. This is a Windows service with multi-threaded handlers, and it may not crash. Ever. Which it will do if any of those threads have an unhandled exception.

Also, those exceptions are written to future bulletproofing. The example I've given in comments below would be adding a factory for the handlers... as the code is written today, there will never be a null task, but if the factory is not implemented correctly, the code could throw an exception. Yes, that should be caught in testing. However, I have junior developers on my team... "May. Not. Crash." (also, it must shut down gracefully if there is an unhandled exception, allowing currently-running threads to complete - which we do with an unhandled exception handler set in main()). We have enterprise-level monitors configured to send alerts when application errors appear on the event log – those exceptions will log and flag us. The approach was a deliberate and discussed decision.

Each possible exception has each been carefully considered and chosen to fall into one of two categories - those that apply to a single datafeed and will not shut down the service (the majority), and those that indicate clear programming or other errors that fundamentally render the code useless for all datafeeds. For example, we've chosen to shut down the service down if we can't write to the event log, as that's our primary mechanism for indicating datafeeds are not getting processed. The exceptions are caught locally, because the local context is the only place where the decision to continue can be made. Furthermore, allowing exceptions to bubble up to higher levels (1) violates the concept of abstraction, and (2) makes no sense in a worker thread.

I'm surprised at the number of people who argue against handling exceptions. If I had a dime for every try..catch(Exception){do nothing} I see, you'd get your change in nickels for the rest of eternity. I would argue to the death1 that if a call into the .NET framework or your own code throws an exception, you need to consider the scenario that would cause that exception to occur and explicitly decide how it should be handled. My code catches UnauthorizedExceptions in IO operations, because when I considered how that could happen, I realized that adding a new datafeed directory requires permissions to be granted to the service account (it won't have them by default).

I appreciate the constructive input... just please don't criticize simplified example code with a broad "this sucks" brush. The code does not suck - it is bulletproof, and necessarily so.


1 I would only argue a really long time if Jon Skeet disagrees

James King
  • 6,233
  • 5
  • 42
  • 63
  • There's a lot of stuff looking wrong with this code. If nobody's helped you by tonight, I'll try to give you some direction with an answer. – Cory Nelson Oct 02 '15 at 18:21
  • Thanks. Usr restored his original answer where we had a long discussion on the things that seem wrong in this code... trust me that everything "wrong" was deliberately considered and is there for a reason. – James King Oct 03 '15 at 17:46
  • Regarding your "update" - making code not crash in the face of exceptions usually (in well-written code) involves *one* top-level catch block... not the many that you've got sprinkled all over the place. – Jon Skeet Oct 07 '15 at 06:12
  • Thanks, Jon... this is in a Windows service, and the problem is that some exceptions will require the service to shut down (gracefully to allow the executing threads to complete), while others will be logged and ignored, so that an issue with one datafeed won't prevent the other 50 from being processed. Those decisions can only be made locally. The question I have is, if generating the exception catches is easy (ReSharper + Exceptional), is there an argument for _not_ catching them that outweighs the need to shut the service down gracefully or continue running on a case-by-case basis? – James King Oct 07 '15 at 09:16
  • There are a lot of guidelines for exception handling floating out there, and for the most part they agree... for each one, I look at the code and see that I'm following, not violating them - even with so many catches. For example: "Only catch exceptions you can handle to rescue the situation" - check. "Avoid exception reporting/logging lower in the call stack" - it's a thread, there's no higher level. "Catch the exact exception types that you expect, because these are the types your code is prepared to handle" - definitely check, and nowhere do I catch `Exception`. – James King Oct 07 '15 at 09:20
  • And again, what's up there is a stripped-down version of what the exception handlers really do... I don't _really_ have empty catch clauses. So do I have a legitimate exception for "usually", or there arguments that counter the requirements I'm giving (and if so, how do I address my requirements in a "correct" way)? I feel like so far, nobody is responding to the specific issues I need to address with anything but a broad general-practice brush. And my extensive use of catch clauses is getting far more attention than the question I'm actually asking :( – James King Oct 07 '15 at 09:31

3 Answers3

2

First, your question: it's not a problem in itself to request lock inside ContinueWith. If you bother you do that inside another lock block - just don't. Your continuation will execute asynchronously, in different time, different thread.

Now, code itself is questionable. Why do you use many try-catch blocks around statements that almost cannot throw exceptions? For example here:

 try {
     task = new Task(() => new DatafeedUploadHandler().UploadDatafeed(this.legalOrg, datafeedFileData), TaskCreationOptions.LongRunning);
 }
 catch {}

You just create task - I cannot imagine when this can throw. Same story with ContinueWith. Here:

this.runningTaskDictionary.Add(task.Id, task); 

you can just check if such key already exists. But even that is not necessary because task.Id is unique id for given task instance which you just created. This:

try {
    fileProperties = this.fileWatcherDictionary[eventArgs.FullPath];
}
catch (KeyNotFoundException keyNotFoundException) {}
catch (ArgumentNullException argumentNullException) {}

is even worse. You should not use exceptions lile this - don't catch KeyNotFoundException but use appropriate methods on Dictionary (like TryGetValue).

So to start with, remove all try catch blocks and either use one for the whole method, or use them on statements that can really throw exceptions and you cannot handle that situation otherwise (and you know what to do with exception thrown).

Then, your approach to handle filesystem events is not quite scaleable and reliable. Many programs will generate multiple change events in short intervals when they are saving changes to a file (there are also other cases of multiple events for the same file going in sequence). If you just start processing file on every event, this might lead to different kind of troubles. So you might need to throttle events coming for a given file and only start processing after certain delay after last detected change. That might be a bit advanced stuff, though.

Don't forget to grab a read lock on the file as soon as possible, so that other processes cannot change file while you are working with it (for example, you might calculate md5 of a file, then someone changes file, then you start uploading - now your md5 is invalid). Other approach is to record last write time and when it comes to uploading - grab read lock and check if file was not changed in between.

What is more important is that there can be a lot of changes at once. Say I copied 1000 files very fast - you do not want to start uploading them all at once with 1000 threads. You need a queue of files to process, and take items from that queue with several threads. This way thousands of events might happen at once and your upload will still work reliably. Right now you create new thread for each change event, where you immediatly start upload (according to method names) - this will fail under serious load of events (and in cases described above).

Evk
  • 98,527
  • 8
  • 141
  • 191
  • That's funny. You picked up a lot of the style problems that I already found in my previously deleted answer. – usr Oct 03 '15 at 09:30
  • @usr well they are just too obvious I suppose, so not hard to spot :) And why did you delete your answer? I didn't see any answers when I wrote mine. – Evk Oct 03 '15 at 15:31
  • 1
    Because my answer was not valuable enough. I just reopened it because of the bounty. Stupid internet points :) – usr Oct 03 '15 at 15:48
  • I'm glad you reopened it, I saw Evk's answer and went ARRRRGH at the thought of having to explain it all again :D ...Evk, see the thread on usr's post. The answer is that those calls *do* throw exceptions... you wouldn't get them on the code I wrote, true, but (1) the service may not under any preventable circumstance terminate, and (2) other developers will work on this and modify the action argument in ways that could return a null and throw those exceptions. The example I gave usr was implementing a factory to generate the datafeed handler - something we _will_ do. – James King Oct 03 '15 at 17:43
  • Also, for anyone who thinks exception handlers are hard to generate, download the Exceptional plugin for ReSharper by Rico Suter... alt+enter will generate the try..catch blocks for you. – James King Oct 03 '15 at 17:45
  • I've read comments under the usr's answer and cannot say they convienced me. But, I don't see a full code and don't know a whole story, so maybe you way of try-catching around blocks that might throw after future modifications is valid (of course I don't think that try-catch blocks is "hard to generate"). Still, if you are writing applicaiton that will upload files using change events from filewatcher - consider my other suggestions, because they are based on experience of writing that same applications. – Evk Oct 03 '15 at 17:55
  • My added rant wasn't directed at you or anyone in particular - I didn't see your last comment until after I'd updated the question. It's hard to have a full-blown discussion on this in messages... but a couple of points: One, task IDs are reused after the thread terminates. On a long-running service, I'm almost guaranteed to get the same thread ID more than once, and if the task hasn't been removed properly, I will crash. The service simply may not crash - ever. (more) – James King Oct 05 '15 at 13:31
  • Part of the problem, as I say, is that I need to make sure that future changes that break the code are still handled... junior developers will modify this code, and if they don't understand how the dictionary is being handled, they can break the code in a way that testing will likely not catch. I really don't understand why so many people are arguing against catching exceptions based on what the code looks like today... the code WILL change, and I need to do everything I can to ensure that what I write will handle what someone could screw up in the future. (more) – James King Oct 05 '15 at 13:34
  • What really irks me, though, is the resistance from everyone to catching exceptions... it's not. that. hard. Seriously :( And have you noticed what you don't see? I'm surprised nobody called me on it... you only don't see it once, but in the full code, you won't see it anywhere. – James King Oct 05 '15 at 13:37
  • Your way of thinking is interesting and unusual, though I still cannot agree on all of your points. There are so many ways how developer who does not even know how dictionary works, can break important code which can never fail (by the way, who will allow such developer edit code without review?)... Reminds me php a bit, which also cannot fail no matter what, even if state might be corrupted. So I'd better put exception handlers for exceptions I know might happen and I know how to handle, and others (unexpected, unhandled) I will just log and restart the service. Well comments are short indeed – Evk Oct 05 '15 at 13:54
  • @JamesKing it's interesting for me, what do you do in those catch blocks? How do you recover if you failed to start Task, failed to add continuation and so on? Service should not fail, but something clearly went wrong, and you don't know what even (that's most likely some junior developer added some other code that failed). – Evk Oct 05 '15 at 14:09
  • Okay, so we receive datafeeds in various directories (sometimes more than one feed per directory). When the service starts, we create one `TrackingFileSystemWatcher : FileSystemWatcher` for each directory watched. Files that are uploaded take significant time to process, so we create tasks for them and return without blocking. If a file is not processed, we notify support/developers/whomever, _but the service continues listening for other datafeeds_. An issue with one datafeed should not stop all of the other feeds from being processed. (more) – James King Oct 05 '15 at 15:31
  • `task.ContinueWith -> ObjectDisposedException`: Not likely to happen unless someone mucks with the code in a way they shouldn't (it happens), but if it does, we log and continue listening for other feeds. I would hope that code terrible enough that every task fails would be caught in testing, but introducing a subtle bug (especially in a multi-threaded application) is not a ridiculous possibility. So my choices are say "eh, it's probably never going to happen, don't bother", or "let me safely catch the exception, no matter how unlikely it is, so that in the case of an unforeseen (more) – James King Oct 05 '15 at 15:43
  • an unforeseen scenario I don't take down the service for problems with one datafeed". The thing I don't get is, _it costs me *nothing* to catch this exception and handle it safely_. So why is catching it an issue? Furthermore, if it _would_ happen, and I haven't bothered catching it, the time spent trying to figure out what went wrong, why the entire service is crashing every time someone uploads a file to the 0048 directory, is ridiculous when compared to the 15 seconds it takes me to add the handler and use standard routines to create an event log entry before abandoning the thread. – James King Oct 05 '15 at 15:46
  • ^--- meant to say "but introducing a subtle bug that crashes one task (especially in a multi-threaded application) is not a ridiculous possibility" – James King Oct 05 '15 at 15:49
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/91404/discussion-between-evk-and-james-king). – Evk Oct 05 '15 at 16:10
  • Work is blocking chat, will write from home :P – James King Oct 06 '15 at 17:59
1

No it will not burn you. Even if the ContinueWith is inlined into to the current thread that was running the new Task(() => new DatafeedUploadHandler().. it will get the lock e.g. no dead lock. The lock statement is using the Monitor class internally, and it is reentrant. e.g. a thread can aquire a lock multiple times if it already got/owns the lock. Multithreading and Locking (Thread-Safe operations)

And the other case where the task.ContinueWith starts before the ProcessModifiedDatafeed finished is like you said. The thread that is running the ContinueWith simply would have to wait to get the lock.

I would really consider to do the task.ContinueWith and the task.Start() outside of the lock if you reviewed it. And it is possible based on your posted code.

You should also take a look at the ConcurrentDictionary in the System.Collections.Concurrent namespace. It would make the code easier and you dont have to manage the locking yourself. You are doing some kind of compare exchange/update here if (this.fileWatcherDictionary.ContainsNonNullKey(eventArgs.FullPath)). e.g. only add if not already in the dictionary. This is one atomic operation. There is no function to do this with a ConcurrentDictionary but there is an AddOrUpdate method. Maybe you can rewrite it by using this method. And based on your code you could safely use the ConcurrentDictionary at least for the runningTaskDictionary

Oh and TaskCreationOptions.LongRunning is literally creating a new thread for every task which is kind of an expensive operation. The windows internal thread pool is intelligent in new windows versions and is adapting dynamically. It will "see" that you are doing lots of IO stuff and will spawn new threads as needed and practical.

Greetings

Community
  • 1
  • 1
Muraad Nofal
  • 794
  • 3
  • 6
  • Thanks, Muraad... those are helpful suggestions and input. I wasn't familiar with ConcurrentDictionary... it looks like it was first available with the .Net Framework 4.0, which we weren't using when I posted. But since then we've decided to migrate before release, so now I can :) I can't use it everywhere, but I think you're right, I can use it for the Task dictionary, at least. Perfect, thanks. – James King Oct 10 '15 at 13:53
  • As for doing the task.ContinueWith and .Start() outside the lock... I think we're thinking the same thing, maybe the same spot. I think I could safely release the initial lock before the task creation and start. It would mean that the next event would grab the same properties, but I only run the task and update if the file changed or was processed more than an hour ago. I could only run into an issue if the user uploaded a changed file while the task was still running, the first task persisted the changes from the second, and the second task failed. Odds vs. benefits = prbly not worth it. – James King Oct 10 '15 at 14:04
  • I´m not sure where ```fileProperties``` is defined? Is it a class variable or local to the ```ProcessModifiedDatafeed``` method? Nevertheless you could define a new variable ```FileSystemWatcherProperties fileProperties2 = fileProperties``` right before your task.ContinueWith and use this in your ```ContinueWith``` lambda. This way each taks gets his own reference to the correct fileProperties value and will persist the right file i think. And you could still remove it from the dictionary based on its id – Muraad Nofal Oct 11 '15 at 23:40
  • By the way I´m totally fine with your exception handling if you want this thing to run forever. And every event and its processing is one independent operation that may fail but is no shutdown/restart reason. I´m a fan or putting stuff that may fail into tasks and do error handling there. They can be a way to encapsulate dangerous stuff. Could be called "throw away threads" :-D If you don´t ```await``` a taks or ask for ```task.Result``` exceptions will simply be eaten up and are ignored. They are not breaking out of the task if you don´t want them to. – Muraad Nofal Oct 12 '15 at 00:18
  • fileProperties is just a simple class we wrote with no purpose other than to hold values. It could be a struct, but we don't want to pass it by value, so we made it a class. That's always the problem with posting example code - too much to include everything. – James King Oct 13 '15 at 02:12
  • I do do the exception handling within the tasks themselves. The only catches you see here are for exceptions that the .NET framework will thrown trying to create the tasks... that would be clearer if I'd included all the catches in place, but like I say - too much to include everything. The .ContinueWith() catches exceptions directly, but the code is also right there... it is its own tasks. – James King Oct 13 '15 at 02:15
0

I have not fully followed the logic of this code but are you aware that task continuations and calls to Wait/Result can be inlined onto the current thread? This can cause reentrancy.

This is very dangerous and has burned many.

Also I don't quite see why you are starting task delayed. This is a code smell. Also why are you wrapping the task creation with try? This can never throw.

This clearly is a partial answer. But the code looks very tangled to me. If it's this hard to audit it you probably should write it differently in the first place.

usr
  • 168,620
  • 35
  • 240
  • 369
  • The constructor for Task(Action, TaskCreationOptions) can thrown either of those exceptions. It's not likely, since I'm explicitly and correctly setting them, but I try to code for people who will change my code down the road... if it's thrown, I catch it. I'm starting the task delayed because I need to do a number of things with it before starting it, including adding it to the dictionary based on the task ID. Even if there's a way to do all those things in one statement when I instantiate the task, I think I would avoid that for clarity. – James King Sep 30 '15 at 15:29
  • As for the code being tangled... nothing of what I'm doing interacts with any of the other things I'm doing, except for the lock (which I expect to execute on a separate thread and be a non-issue) (but is what I'm asking about). Basically, it (1) locks a dictionary that stores properties of the files that have changed so that it can update the current event's file in a thread-safe manner... (2) retrieves those properties, or creates a new set of properties if this is the first time I've seen the file... (3) creates an (unstarted) task that accepts the file data and processes it... – James King Sep 30 '15 at 15:37
  • (4) locks the dictionary that holds references to all running tasks so that it can add the newly created task in a thread-safe manner... (5) adds a reference to the task to the dictionary... this is necessary so that I can tell the OnStop() command of the service to wait for the currently running threads to complete before shutting down. (6) it adds a ContinueWith() to the task that removes itself from the dictionary when it has completed. I can't see any other way to do this... because it's a windows service, there's no main thread that could block or poll, even if I wanted to – James King Sep 30 '15 at 15:44
  • (7) The ContinueWith() removes itself from the dictionary, then persists a record of when the file was last executed. This is necessary because a file could be updated while the service was down... I need to know whether to process the file when I start up or not (requirement #5, never process a file more than once). And there's where the question is... I need to lock the file properties dictionary to persist it, but I need to be sure I'm not going to deadlock or get burned by something I'm not aware of. I'm always open to criticism and input, but suggestions have to meet the rqmts – James King Sep 30 '15 at 15:50
  • 1
    "if it's thrown, I catch it" and what do you do about it? It's an API usage error (a bug). It can't be handled and corrected. That said this answer is pretty worthless and I'll probably delete it. – usr Sep 30 '15 at 16:15
  • "catch (ArgumentNullException argumentNullException) {}" This is also a bad code smell. I would so fail that in a code review. Using exceptions for control flow is dangerous. It's prone to swallow bugs and dog slow. – usr Sep 30 '15 at 16:16
  • I guess the delayed task start is one of the very few valid places that I have seen to use Start. Here's an alternative: Make the task add itself to the dictionary (`Task.Current.Id`). You don't need the continuation either. The task's body should be `AddID(); RunStuff(); RemoveID();`. The task can even generate the ID by itself. – usr Sep 30 '15 at 16:19
  • And you can replace runningTaskDictionary+lock with ConcurrentDictionary (maybe). – usr Sep 30 '15 at 16:20
  • I'd like to understand your first statement better... what is dangerous about reentrancy? If the code executes somehow on the same thread, no issue, I won't deadlock (reentrancy). I'd expect that TaskCreationOptions.LongRunning would hint the scheduler whether it can inline (inlining was new information for me, thanks), but if not, no harm, because the task isn't started until after the ContinueWith() is defined. If you still think this is dangerous, can you elaborate? Other suggestions on how to attack this are welcome. – James King Sep 30 '15 at 16:33
  • Sorry, delayed send in that last message... looking at your four comments – James King Sep 30 '15 at 16:34
  • ""if it's thrown, I catch it" and what do you do about it? "... If I don't catch it, starting with .NET 2.0, the framework will shut down the entire application (the windows service). The errors I catch fall into two categories... those that are bad but shouldn't stop us from processing other files, and those that are going to stop us from processing other files regardless. In both cases, I log to the event log (monitored by OpenView or whatever we're using); devs investigate the first case; maybe it's us, maybe it's the uploader... the second case I gracefully shut down the app with – James King Sep 30 '15 at 16:38
  • with an `AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;` in the main service. I like to catch all possible errors so that (1) I can exit gracefully in all cases, and (2) so that I've considered all possible scenarios where the exception can be thrown - such as an UnauthorizedAccessException making me think of the scenario where someone sets up a new feed in the config file, but doesn't set permissions on the folder we're listening to... without detailed log info, that'd be a bugger to figure out. – James King Sep 30 '15 at 16:41
  • More dicussion on whether catching exceptions is really control flow should probably go to chat (SO is complaining), but I always validate arguments and parameters before calls... most of those exceptions would mean someone mucked with something they shouldn't have. For errors like unauthorized access, try..catch is the best practice for errors that are unexpected exceptions... testing every time I make the call vs. one 'expensive' exception on the rare occasion it would happen. – James King Sep 30 '15 at 16:46
  • I'll look into a ConcurrentDictionary, thanks... I do need the continuation though, because I can't block while I process a file with 80,000 lines in it. I'll look at your other suggestions, as well, but I'm hoping someone can still answer the main question of whether this would be an issue (and more importantly how)... if for no other reason than a better understanding. – James King Sep 30 '15 at 16:50
  • "what is dangerous about reentrancy" It's just difficult to manage. Here, I don't see any particular problem. That said I have not fully analyzed the code.; Regarding the catch strategy, if you can't correct the error you probably should let it bubble up. There should be one top-level try-catch that logs all uncaught errors. In general the log-and-continue strategy is a good thing for production services. Just don't litter the code with log statements. UnhandledException also is a good thing. – usr Sep 30 '15 at 19:51
  • "control flow": Here, you are unambiguously using exceptions for control flow. Bad! There is no need. Just use ContainsKey or TryGet or something. The workaround is even less code. Force yourself to change that habit! – usr Sep 30 '15 at 19:52
  • "I can't block while I process a file with 80,000 lines in it" don't do that, then. The code in the task does *not* execute under the lock. It's in a separate thread. You can control locking in that task separately. – usr Sep 30 '15 at 19:53
  • I can't let exceptions bubble up without .NET aborting the entire process and shutting down the service - not an option. (1) I'm not going to stop processing 150 datafeeds because someone screwed up and deleted one folder, and (2) .NET won't process my OnStop() and shutdown logic - anything I'm in the middle of becomes a mess. Graceful shutdown is critical to any windows service, and I'd argue that under NO circumstances should you allow a service to crash, and I'd argue that it's a terrible practice as strongly as you're arguing that it's okay. Probably not going to agree on that – James King Sep 30 '15 at 20:28
  • You can let exceptions bubble up to the caller of ProcessModifiedDatafeed. If that caller is the framework then you should not catch at the place you create the task. You should wrap the entire method with a try-catch. Why catch in all kinds of small places? Catch everything in one central place.; Of course the process should not crash. I'm not suggesting that. You are quite enlightened with regards to error handling. Most .NET devs are not this rigorous. I'm suggesting a small improvement to your existing strategy. "as you're arguing that it's okay" no! – usr Sep 30 '15 at 20:36
  • on that one, but it's fine - I have a requirement to shutdowns gracefully when we can't continue processing, so it's moot. Could you specify what line you think I'm using exceptions for control flow for? Lines like `if (this.fileWatcherDictionary.ContainsNonNullKey(eventArgs.FullPath))` test parameters before entering try blocks... the catches on new Task(..) _do_ bubble the exception and are caught at the service level for a graceful shutdown - because they are catastrophic and can only happen if one of my team changes code without testing properly. But I will absolutely log it first. – James King Sep 30 '15 at 20:42
  • Every possible exception was considered, a scenario envisioned, and a decision of whether to log a warning or exception, ignore it, or shutdown the service was made as a calculated decision. Those decisions must be made locally, because locally is the only place I have the information to make the call. For a datafeed service that controls user access, these are critical decisions. Maybe the issue is just that you're not seeing all of the code, dunno. – James King Sep 30 '15 at 20:45
  • Sorry if that's coming across testy or flaming, it isn't meant that way. I do want to look into your suggestions, and it sounds like you don't think the 'nested' lock is an issue... hoping to get more weigh-in to see if anyone has something we haven't considered. – James King Sep 30 '15 at 20:47
  • You are calling ContainsNonNullKey. Therefore, the exceptions that you are catching are not possible. Except, if you make a mistake and it's a bug. In that case it should be logged and the function aborted. But you are swallowing that error. That's a problem.; "the catches on new Task(..) ". What exception do you think is possible here? There is none. If you think there might be a bug, let the exception bubble up to the global logging handler. There should be a handler wrapped around the entire function.; – usr Sep 30 '15 at 20:51
  • "Those decisions must be made locally" That's a valid point. I would suggest that you catch those exceptions locally, wrap them in a custom `MyServiceException` and let them bubble up the the global handler. The global handler then logs them. Then it does `if (!(ex is MyServiceException)) throw;` to kill the process in unknown/unsafe circumstances. That way you only need logging code once and you get the automatic "back-out" that exception handling provides.; – usr Sep 30 '15 at 20:51
  • "Sorry if that's coming across testy or flaming" not at all. We are discussing at a level that is unavailable to most developers. It's refreshing to do that. I would like to see you answering on Stack Overflow. You are qualified. – usr Sep 30 '15 at 20:51
  • "the exceptions you are catching are not possible". Exactly. The only way they're possible is if someone changes it in a breaking way - writes a factory that instantiates different handlers, for example, and inadvertently returns a null instead of throwing an exception if the handler can't be instantiated. Hence saying that if it's a documented exception, I will handle it, regardless of what the code looks like today. I'm the lead developer of a team with a number of junior developers, and I've seen far less forgivable oversights :) (The factory IS actually going in with the next sprint). – James King Sep 30 '15 at 21:07
  • And the code you're not seeing on that is code where I spank the developer in the event log for not testing all code paths... and that one does bubble up and shutdown the service - if we can't spawn tasks, the service is worthless. – James King Sep 30 '15 at 21:09