I was looped into a question from a customer today regarding how to best handle exceptions generated by subordinate threads from within the owning thread. They were using threads as a mechanism to do a set of primary tasks in parallel. By primary I mean these aren’t just actions that disappear into the background to be executed “at some time in the future,” but rather had normal scheduler priority and were responsible for the main workload of the application.
Thanks for all of the answers to the quiz. I appreciate the involvement, and most of the feedback was exactly the kind of thing we’re looking for.
So you may or may not have noticed that System.IO.Stream
both has a Close()
method and implements IDisposable
, meaning it has a Dispose()
method, too.
(Note: it’s explicitly implemented, meaning that to access it you’ll need to
use an IDisposable
typed reference in C#, e.g. as in
((IDisposable)myStream).Dispose()
). Stream
is an abstract base class, the most
common derivitive being FileStream.
I made a couple small improvements to my brain dump from last night.
I added a few continuation-ish constructor and Wait(...)
overloads that take a consequent and alternate Action<T>
(a new delegate type in Whidbey).
Action<T>
is just a void(T)
function that executes a set of statements (with no return value).
If the guarded condition is met, the wait class just executed your consequent from within a locked context;
if not, it will execute the alternate from the same context.
A guarded wait is a locking primitive which enters an object’s monitor and performs some processing once a certain predicate condition arises. You could imagine a buffer which refills itself once its contents have been consumed, a consumer which empties a buffer once it reaches a certain capacity, and so on.
Lazy, functional streams go hand in hand with thunks. They enable lazy loading and calculation of algorithms, and can be particularly interesting due to the ease with which they compose. The examples I provide below make heavy use of new C# 2.0 features, such as anonymous delegates and iterators. Some will notice my undeniable influence by the Wizard book in the concepts presented here.
Generating and dealing with Scheme lambda expressions in IL is a relatively interesting problem. Scheme is statically-scoped, making the implementation a bit more straightforward than, say, Common LISP. I have not yet determined the best approach in all cases yet, but certainly have a workable approach. There are many optimizations to be had, but I’ll worry more about this at some point in the future. Interestingly, the approach I have arrived at is very similar to C# 2.0’s anonymous delegate syntax, albeit for the auto-generated delegates.
A thunk is a relatively common construct in functional programming where the passing of arguments won’t result in any side effects. In these cases, the compiler can silently emit code that bypasses computing the value of an argument altogether at the call site in case it isn’t used in a method body at all. If it does end up being used, however, it will be lazily evaluated at the last possible moment. This is sometimes referred to as call-by-lazy-evaluation. It is said that the argument passed is frozen at the time of invocation, and thawed when it is needed. Further references typically avoid re-thawing once the initial thaw has ocurred.
Here’s some interesting reading on functional programming.
Simply put, contract based programming formalizes the notion of a program’s input/output constraints, supplying first order language constructs to verify and prove correctness. This includes, but is not limited to, the expression of type invariants, pre- and post-conditions, reliability guarantees in the face of unexpected failures, and side-effecting state modifications. For instance, a method which performs division certainly does not want to accept a denominator equal to zero, and an atomic operation producing a return value should always ensure that certain type invariants and consistent state constraints remain true at the end of its execution. Similarly, code which affects the environment in which it executes likely should indicate to callers what to expect should a failure occur; i.e. does an operation make (verifiable) guarantees regarding what actions it will take and succeed at to attempt recovery, or does it simply halt execution and throw an exception? One example of a language which employs such concepts is Eiffel. (I recommend this article for a bit more detail.)