 Saturday, February 18, 2006
There were some good ideas in the comments on my Random Reflection Trivia post. Wilco had some good thoughts about mangled names (having both MyAttribute and MyAttributeAttribute defined in a project) as well as type forwarding...both of which were very reasonable explanations but not quite what I was getting at.
So...enough mystery. What was the magic line of config that made everything break? It was this:
<trustLevel="Medium">
Yes, this is related to partial trust and the joys of [AllowPartiallyTrustedCallers] (a.k.a. APTCA).
In my situation, I had an attribute (MyAttribute) defined in a fully-trusted but non-APTCA assembly. This attribute was applied to a type (Foo) defined in a partially trusted assembly. As a rule, partial trust can't call into full trust unless that assembly has the APTCA bit set. And this applies to attribute constructors and property setters in the same way it applies to any other code.
When I ran into this originally, I had a brilliant idea -- why not just assert FullTrust before making the call to IsDefined()? Guess what...it still didn't work.
It turns out that the framework's implementation of GetCustomAttributes() will push a fake stack frame onto the CAS evaluation stack prior to instantiating the attribute type. This stack frame contains the security context of the assembly containing the type to which the attribute was applied. This appears before the stack frame containing the full trust assert, and as such the CAS stack walk will result in insufficient permissions. The net effect is that custom attribute constructors always run in the security context of the type to which they were applied. Asserting full trust from calling code won't get around this.
After thinking about it for a while, that behavior makes some degree of sense to me. What I don't understand is the rationale behind swallowing the resultant SecurityException. The current implementation just happily carries on -- returning false from IsDefined() and an empty array from GetCustomAttributes().
But it is what it is.
 Wednesday, February 15, 2006
Here's a fun one that had me scratching my head for a bit last night. Given the following class definition: [MyAttribute] public class Foo { ... } Under what circumstances will typeof(Foo).IsDefined( typeof(MyAttribute), true ) return false? Hint: typeof(Foo).IsDefined( typeof(MyAttribute), true )
can be made to return true by changing one line of config.
 Monday, October 24, 2005
 Saturday, July 30, 2005
As several people correctly pointed out, my recent issues with string comparison were due to string interning.
String interning is a (usually) transparent process where by the CLR tries to guarantee that two strings that are value-equivalent are also reference-equivalent. Usually, but not always. The interning rules are quite complex, and I don’t claim to understand them all.
I didn’t write the code that was doing the comparison, so I didn’t have the ability to turn the reference equality comparison into a value comparison (String.Equals()). The only thing I could do was change my code to return String.Empty instead of the compile-time constant “”.
Guess I’ll be more dilligent about returning String.Empty in the future.
 Friday, July 29, 2005
I lost a good hour to this last night…too much time staring at the debugger output and trying to convince myself that I wasn’t completely crazy.
Say you have the following code: string str1 = "";
string str2 = "";
Debug.Assert( (object)str1 == (object)str2 );
Under what conditions will this assert fail?
 Thursday, March 24, 2005
I'm curious about how people are building solutions using ASP.NET pages and ASMX web services today. Do you intermix .ASPX pages and .ASMX services together in the same site, or do you separate out the two different types of content into different virtual directories?
What pushes you toward one approach or the other?
 Sunday, February 06, 2005
This
is pretty cool – Mike Stall has
posted the source code for Blue, a C# compiler written in C#. It’s not a
complete implementation of the language, but it does bootstrap (it’s
capable of compiling itself). I’m interested in cracking it open and
taking a look at the lexer/parser implementation, and finally seeing a
real-world example of Reflection.Emit…
In the mean time, check out Mike’s
writeup or just go download the source
yourself.
 Thursday, January 27, 2005
Scott lays out an interesting little
idiom over here.
Basically, he’s proposing an ‘aggregated’ or
‘lifted’ version of ToString() that would render this:
Person p = new
Person("Scott","Hanselman",new DateTime(1974,1,22);
string foo = p.ToString("My name is {FirstName} {LastName} and my birthday
is {Birthdate:MM/dd/yyyy}");
Semantically
equivalent to this:
string foo =
String.Format("My name is {0} {1} and my birthday is
{2:MM/dd/yyyy}",p.FirstName,p.LastName,p.BirthDate);
It’s
definitely syntactic sugar, but I’ve got a notorious sweet tooth and I
like it. However, I don’t think I’d do this in a class library for
a couple of reasons:
·
Performance. Really the only way to
implement this would be via Reflection, and reflection is slow relative to
native member access. I expect a library implementation of Scott’s idiom
to be about
an order of magnitude slower than native member access. That amounts to
expensive sugar.
·
Lack
of type safety.
The format string in Scott’s suggestion is coupled to the type of target object.
If Person doesn’t expose a field/property called FirstName, the only way
you’ll find out about it is via an exception at runtime. Furthermore,
because the member names are embedded inside of opaque strings, tools like
CodeSmith and Resharper can’t include them in a Rename refactoring. This
could impeded productivity on large projects that make heavy use of this idiom.
I do think it’s
pretty cool, though. My original thought was this could be a pretty cool
language feature, implemented by compile-time code generation much the way
anonymous delegates are created in C# 2.0. Thinking more about it, though, I’m
not sure how you would disentangle the member references from the formatting
string so as to make them visible to the compiler without reducing back to
something nearly identical to the existing String.Format().
Language design is hard.
 Tuesday, December 21, 2004
Extra!
Extra! Generics CLS-compliant in Whidbey!
Peter Drayton comes bearing good news for people who like .NET 2.0 Generics –
they’ll be CLS-compliant in Whidbey. This means that developers no longer
have to make a choice between the power of generics and the cross-language
support implied by strict CLS compliance.
It looks like the CLS rules will be amended to require all languages to support
at least the consumption of generic types – you might not be able to
write new generic types or methods, but you’ll at least be able to
consume them. This is goodness.
 Sunday, December 05, 2004
A
while back, Omar posted about one of my big pet peeves about Visual Studio: the
fact
that the “Build Clean” function doesn’t actually clean the
build. Instinctively, I’d expect this to do something rational (like
clean out all the intermediate build artifacts) – but no, it just sits
there doing nothing.
Fast-forward to now, and he’s built a little shell extension called CleanSources
that does what VS.NET purports to do, but doesn’t. From Omar’s
wiki: This Application does one thing. It adds
an explorer shell menu to folders that when selected will recursively delete
the contents of the bin, obj and setup folders. If you have a .NET project that
you wish to share with some one, this is useful to remove the unnecessary stuff
from the folder before you zip it up and send it off.
+1
for Omar. Thanks, man.
 Friday, November 05, 2004
Sam is so totally right –
TestDriven.Net rocks. RightClick
-> Test In Debugger is a godsend. That’s my favorite feature, but
there are many others that make this tool worth checking out.
Jamie, you kick ass. Thanks for
an awesome tool.
 Friday, October 15, 2004
You’ll
have to wait until the release of next week’s Community Tech Preview
release to get it, though. Soma has the details
here.
At
least everyone can stop asking for it now J
 Monday, October 04, 2004
I’m a huge fan of ArsTechnica. Through the articles he’s
written, Jon Stokes has taught me a ton of stuff about low-level principles of
CPU design and actually made all that CompE stuff interesting. It’s by
far my favorite site for general techie-related stuff.
Anyway, I noticed that the finally unveiled
their new site design today. It looks really great, but this entry in the Redesign FAQ caught my
eye:
Q. Why did you change over from Linux?
A.
This is a loaded question, so we'll be brief. Ars started out on Windows NT
back in 1998, but shortly after that we moved to FreeBSD, and then later,
Linux. We ran Linux until March of 2004, when we made the move to Windows
Servers. Linux and Apache had served us quite well, but when we turned to look
at building our new CMS, .NET was simply so
attractive for our needs that we felt it warranted the switch. If
there are enough requests, we may do an article later documenting our thought
process, but for now I'll say that the
decision was largely a programming one, with the added benefit of
the fact that more of us support Windows in our real lives than Linux. (emphasis mine)
Glad to see more people recognizing that
.NET is just a kick-butt platform to write software on.
 Wednesday, September 22, 2004
Loading an
arbitrary assembly into a design-time tool in order to do interesting things
with it seems to be a somewhat common behavioral pattern for development tools.
I know of several developer tools that have this general feature –
Reflector is the canonical example. We have a couple of internal tools at work
that do the same sort of thing. They all have to solve the problem of loading
an assembly that lives outside of the application’s BaseDirectory.
Loading such an
external assembly via Assembly.LoadFile() is straightforward, provided that the
assembly you’re loading does not have dependencies on other assemblies
that must also be loaded. If those dependencies are not easily located by CLR
Assembly Loader (either by living in the GAC or running app’s probing
directories), you’ll get an exception unless you do a little bit of extra
work. You’d like Fusion to probe for dependencies in a “working
directory” – the directory from which you’re loading the
assembly, but it won’t do that automatically. Fortunately, this work is
minimal but obscure.
The solution is
to subscribe to the AppDomain.ResolveEvent before you attempt to load the
external assembly. This gets fired by the loader just before an exception gets
throw, and gives your app a last-second chance to try to find the assembly. You
can then attempt to do an Assembly.LoadFile() from whatever directory you want.
It’s very easy to use this event to implement a “working directory”
concept. All you have to do is keep track of the directory from which you
loaded the original assembly and use the ResolveEvent to probe there too.
Here’s
some example of a quick-and-dirty implementation that might live on some form
somewhere:
private void button1 Click(object sender, System.EventArgs e)
{
FileDialog d = new
OpenFileDialog();
if( d.ShowDialog() ==
DialogResult.OK )
{
string fileName =
d.FileName;
this.workingDirectory
= Path.GetDirectoryName( fileName );
Assembly asm = Assembly.LoadFile( fileName );
DoInterestingThings( asm );
}
}
private Assembly
CurrentDomain AssemblyResolve(object
sender, ResolveEventArgs args)
{
//Args.Name is the assembly’s full name (“MyAssembly,Version=
[etc]”)
//We want just the part before the comma so we can derive the filename
string fileBase =
args.Name.Split(',')[0];
if( workingDirectory
!= null )
{
string fileName = fileBase +
".dll";
string workingPath
= Path.Combine( workingDirectory, fileName );
return
Assembly.LoadFile( workingPath );
}
return null;
}
And,
of course, the event handler wireup:
//Somewhere
in your initialization code
AppDomain.CurrentDomain.AssemblyResolve
+= new
ResolveEventHandler(CurrentDomain AssemblyResolve);
There are still
ways when this might fail, but it does handle the common cases. Of course, all
the standard caveats of Assembly.LoadFile() apply – no load context and
whatnot – but it works well for a dev tool.
 Tuesday, August 31, 2004
FYI
-- The Beta 1 Refresh (with Team System bits) is available for download on
MSDN.
Go
nuts.
 Friday, August 13, 2004
I confess,
I don’t do a ton of VB.NET coding. Check that – I don’t do
any VB.NET coding. I’m strictly a curly-braces-and-semicolons kind of
guy. However, I’m finding myself responsible for a code generator that
needs to be bi-lingual between C# and VB.NET. As a result, I’m sort of
forced to code VB by proxy…
Anyway, I wanted to toss out a question to the more VB-minded readers of this
blog: what are the best practices around setting the RootNamespace property in
the project options? On large projects, do you set this to something and omit
namespace declarations from the class files, or do you leave it blank and
declare namespace membership explicitly, a la C#?
Personally, the whole RootNamespace feels like pure evil to my C# mind. The
thought that my class might actually not be in the namespace it declares seems
nefarious at best. I can see how this would be a valuable feature to a VB6
developer who might be namespace-phobic, but I’m curious how this feature
gets used on enterprise-level projects.
 Wednesday, August 04, 2004
Diving deep into the implementation of streams in Cw has given me a new appreciation for the power of iterators.
Wes has posted some really cool stuff about how iterators can be applied to problems other than simple iteration. I haven’t quite parsed all of it yet, but his ideas are pretty interesting. Raised my eyebrows a bit, certainly.
Update: fixed the link
 Saturday, July 31, 2004
COmega (or Cw, as most people seem to be referring to it) is proving to be the source of all sorts of interesting things. If you’re interested in compilers or programming languages, Cw has layers upon layers of new and nifty stuff.
Frankly, I’m not sure what’s more interesting to me: the code that Cw lets you write, or the code that Cw writes for you. I started looking at Cw output in an effort to figure out what was causing the bug that David Findley noticed. The bug David found dealt with lifted member access over a stream of Int32’s. I’ve slightly modified his example to work over a stream of strings, for reasons that will become clear later. However, before we get to that, there are some more fundamental ideas that are worth exploring.
Basic Streams in Cw
Let’s say that you have a function that returns a string* (a stream of zero or many strings), like so:
public string* FromTo( char a, char b ) {
for( int i=(int)a; i <= (int)b; i++ ) yield return ((char)i).ToString(); }
Using this function, I can say something like string* letters = FromTo( 'A', 'E' ); and end up with a variable whose logical contents are {“A”, “B”, “C”, “D”, “E” }. I say “logical contents” because the contents of the stream are produced at consumption time, not creation time. This is sort of counterintuitive at first, as the body of the FromTo() method generally doesn’t execute when you call it . In order to get the yield return statement to run, I need to iterate over the stream with some sort of foreach statement. For example, I could say:
foreach( string s in letters ) Console.Out.Write( s );
and get ABCDE printed out on the console window.
If all of this seems sort of familiar, it’s because iterators in C# 2.0 also use the yield return contextual keyword to do the same sort of thing. I think they also use the same general implementation so I believe that most of what I’m about to say will also hold generally true for C# 2.0 iterators as well.
What’s really happening inside of FromTo()?
Running the compiled output of the FromTo() function through Reflector reveals the following code (I’ve cleaned up the variable names a little bit and removed some decompiler spam for clarity ):
public IEnumerable FromTo(char a, char b) { fromToClosure closure = new fromToClosure(); closure.this value = this; closure.a = a; closure.b = b; return closure; }
What we have here is the creation of a “lexical closure” – an object that effectively takes a snapshot of some stuff that currently lives on the stack and sticks that state on the heap so it can be referred to later. In this specific case, the code is just capturing the current values of the two method parameters and storing them in fields of the same name on the closure object. Once this is done, the function returns the closure back to its caller. Thus, when you call FromTo( 'A', 'E' ), the net result is an concrete implementation of IEnumerable that has two character fields – one whose value is ‘A’ and one whose value is ‘E’.
Implementing streams as enumerable closures
The fromToClosure type provides implementations of both IEnumerable and IEnumerator, allowing the contents of the closure to be traversed using foreach. The IEnumerable.GetEnumerator implementation is simple:
IEnumerator IEnumerable.GetEnumerator() { return ((fromToClosure) base.MemberwiseClone()); }
That is, it just returns a clone of the “world as it was” at the time the closure was created.
According to the IEnumerator pattern, the function of IEnumerator.MoveNext() is to return the “next value” in the stream. Thus, we’d expect the guts of the yield return statement from the original FromTo() source to end up in fromToClosure.MoveNext(). Looking at the decompiled output of this method reveals that this is exactly where that code went (again, I cleaned up the decompiler output and numbered the lines a bit for clarity).
public override bool MoveNext() { char currentCharacter; switch (this.current Entry Point) { case 0: { (1) this.i = ((int) this.a); break; } case 1: { (2) this.i++ (3) if ( this.i <= ((int) this.b)) { (4) currentCharacter = ((char) this.i); (5) this.current Value = currentCharacter.ToString(); (6) this.current Entry Point: = 1; (7) return true; } } } (8) return false; }
If you stand back and squint a bit, it’s easy to see the original for loop in there. The code that started out as
for( int i=(int)a; i <= (int)b; i++ ) yield return ((char)i).ToString();
has been cracked open by the Cw compiler and turned into this implementation of MoveNext(). The initializer statement can be found at (1) – although it’s now initializing a private field on the closure object instead of a local variable. The original conditional statement is replicated almost verbatim in (3), and the increment statement now lives at (2). The body of the loop live at (4) – (6), and the return statements at (7) and (8) ensure that MoveNext() properly returns false when the enumerator has reached the end of the stream.
Stepping through this code a couple of times with a mental debugger should illustrate a significant behavioral difference between streams and a standard foreach: iterators have “lazy list” semantics because the contents of the iteration are not produced until MoveNext() gets called – which might be long after the original call that created the iterator.
Member lifting and implicit iteration
Cw has a really interesting language feature called “member lifting”, which allows for an implicit iteration over each element in a stream. Remember that the outcome of FromTo( 'A', 'E' ) is the stream {“A”, “B”, “C”, “D”, “E”}. Using member lifting, we can transform that stream into the modified stream {“a”, “b”, “c”, “d”, “e”} by saying FromTo( 'A', 'E' ).ToLower(). Given this statement, the Cw compiler will generate code to lift the ToLower() operation out and apply it individually to all elements in the stream returned by the call to FromTo().
So, how does this work? We can get some clues into the implementation from the type system. The static type of FromTo( 'A', 'E' ).ToLower() is string*. This makes sense because an individual call to ToLower() returns a string and we’re aggregating a whole bunch of those calls. Since we’ve already seen how streams are implemented as closure types, it’s expected that this statement would compile out to an instantiation of a closure type. And since there are two method calls in the original statement, we would expect that there would be two calls two methods implemented on this closure type. Looking at the decompiled output, we see that this is indeed the case.
First, here’s the input function as written in Cw:
public string* SimpleLift() { //The ToLower() call lifts correctly return FromTo( 'A', 'E' ).ToLower(); }
And here’s what the compiled version of this function looks like in Reflector:
public IEnumerable SimpleLift() { toLowerClosure closure = new toLowerClosure(); closure.thisValue = this; closure.streamToLower( closure.thisValue.FromTo('A', 'E') ); return closure; }
The invocation of FromTo() is fairly obvious – it’s just capturing the current value of the this pointer in the closure object, and then using that to invoke FromTo(). What’s more surprising is the implementation of the streamToLower() function on the generated closure class. That actually looks like this:
IEnumerable streamToLower(IEnumerable Collection) { toLowerClosure.foreachClosure closure = new toLowerClosure.foreachClosure(); closure.Collection = Collection; return closure; }
Hey, wait a minute! Instead of doing any work, the dang thing just returned another closure! When does work actually get done?? This behavior makes sense – member lifting is accomplished lazily using iterators, and the common implementation of iterators and streams has already been explored. Calling SimpleLift() does nothing except instantiate a bunch of closures and return an IEnumerable implementation. As with all streams, the work won’t really get done until someone iterates over the stream and causes MoveNext() to get called.
The implementation of MoveNext() that we're interested in lives on the generated toLowerClosure.foreachClosure type. In Reflector, that implementation looks like this:
public override bool MoveNext() { IEnumerable fromToResults; switch (this.current Entry Point) { case 0: { (1) fromToResults = this.Collection; if ( fromToResults == null) { return false; } (2) this.foreachEnumerator = fromToResults.GetEnumerator(); if ( this.foreachEnumerator == null) { return false; } } case 1: { (3) if (!this.fromToResults.MoveNext()) { return false; } (4) string text2 = this.foreachEnumerator.Current; (5) this.currentValue = text2.ToLower(); this.current Entry Point = 1; return true; } } }
This MoveNext() is a little more complex because it does two things – due Cw’s lazy evaluation rules, both the FromNext and ToLower productions need to happen on every iteration of the loop.
Line (1) obtains the results of FromTo() that were captured when the stream was created. Although this variable is typed as IEnumerable, it has a concrete type identity of fromToClosure(which is totally reasonable, since that’s what FromTo() actually returns). Line (2) obtains an enumerator, causing the closure to return a clone of itself and its captured state.
Line (3) triggers the production of an element of the FromTo stream. The details of this operation have already been explored in detail, so there’s no need to rehash them hear. The interesting thing to note is that we’re just now getting around to executing yield statement inside of FromTo(), even though it seems like we called that function an eternity ago.
Assuming that the FromTo stream wasn’t at the end and actually yielded an element, line (4) retrieves the produced value and line (5) finally gets around to calling ToLower() on it. This value gets returned all the way back to whomever is foreaching across the stream returned from SimpleLift(), and the whole process repeats until there are no more elements left in the stream to process.
Why bother with lazy lists?
I’m sure there are a number of Haskell programmers out there who could elaborate on the virtues of lazy lists far better than I. However, one benefit of the lazy list pattern that particularly strikes me is the memory cost of this method compared to traditional approaches. Non-lazy (motivated? Protestant?) lists have an O(n) memory cost associated with processing them, because all nodes in the list are memory resident. With the lazy list pattern, because items are produced on demand there’s only one element in memory at a given time. This O(c) characteristic can be very useful when dealing with large numbers of large things.
Ok, so what’s causing David’s bug?
Popping several stack frames back to the issue that originally triggered this post: if member lifting seems to work in the general case, what’s causing the behavior that David noticed? If I can lift ToLower() over a stream, why can’t I lift ToString()? Sadly, after all of this, I don’t have a root cause analysis. But I do have an observation:
public void BrokenLift() { //The ToString() call lifts incorrectly FromTo( 'A', 'E' ).ToString(); }
Does not compile into the standard closure pattern that ToLower() does. Instead, we get this:
public void BrokenLift() { Unboxer.ToObject(this.FromTo(65, 69)).ToString(); }
Rather than generating a closure to wrap the implicit iteration, the Cw compiler is doing something decidedly different. Looking at this output, I’m pretty sure the incorrect behavior is caused by a bug in the Cw compiler rather than a confusion as to the expected results. If the Cw developers were for some reason interested in prohibiting member lifting for methods inherited from Object, I’m guessing they would have simply emitted “this.FromTo(65, 69)).ToString()” and skipped the redundant call to the unboxer.
After blog mint: Check out this interesting presentation on the Common Compiler Infrastructure that Cw makes use of: http://research.microsoft.com/Collaboration/University/Europe/Events/dotnetcc/Version2/Crash%20Course.ppt
 Thursday, July 22, 2004
I’ve had some real fun tonight playing with the COmega compiler from MS Research. If you’re at all interested in programming languages and haven’t downloaded the compiler yet, you owe it to yourself to check it out. This project is much farther along than the F# compiler released last year. The MSR folks have implemented a full VS.NET language service (using the VSIP Babel API’s, I presume). You get VS.NET project support, syntax highlighting, and limited Intellisense. Plus, you get a .chm file that’s pretty extensive for a research project. Overall, the development experience is quite good.
Anyway, on to the interesting stuff. COmega has done a number of really cool things. Aside from implementing the X#/Xen stuff around XML and SQL integration, they’ve incorporated the concurrency syntax from the Polyphonic C# research effort. Polyphonic C# is a set of language-level syntactical primitives designed to make asynchronous programming easier by lifting support for various synchronization primitives out of the API and into the language itself.
The concurrency extensions in COmega add a couple of new syntactical elements to the C# language – async’s and chords. An async is a cross between a method and a counting semaphore. Like methods, async’s can be called. However, async’s don’t declare a set of body statements – instead, they are strictly a blocking primitive that behaves similarly to a counting semaphore. Every time you “call” an asyc, you stick a message on its queue. Operations can wait on an async – if a message exists in the queue, it will be dequeued and the operation will proceed. If there are no more messages left in the async, the operation will block until one appears (just like blocking on a semaphore until it becomes signaled). Blocking on an async is accomplished through the use of chords.
A chord is a language-level representation of things that must occur simultaneously (the way a chord is composed of several simultaneous notes in music – hence the “Polyphonic” nature of Polyphonic C#). Here’s a very simple example of asyncs and chords taken from the COmega docs: public class Buffer { public async Put(string s); public string Get() & Put(string s) { return s; } }
Here we have a simple class that defines one async ( Put ) and one chord (Get). The “Get() & Put( string s)” construct means that anytime somebody calls Get(), the operation will block until a message appears on the Put() async. Once that happens, the body of Get() will execute. It’s interesting to note that the body of the Get() chord has access to the string passed to the Put() async, even though Get() itself is called with no parameters.
Using asyncs and chords, you can do some very interesting things. This, for example, is the equivalent of a C# lock statement:
//The lock object async syncRoot();
//Aquire the lock by waiting on syncRoot() public DoSomthing() & syncRoot() { MyThreadSafeFunc(); //release the lock syncRoot(); }
Using asyncs and chords, it’s possible to implement constructs similar to Thread.Join. The COmega has a pretty good discussion of how to do this (see the “Asynchronous Call and Return Patterns” tutorial), but it doesn’t cover anything like WaitForSingleObject() or WaitForMultipleObjects(). They’re pretty easy to do yourself, though.
Of the two, WaitForSingleObject ( a.k.a “Select” ) is the easiest to implement. Basically, you define a class to represent the Select operation. This class defines a private async member (which the WaitOne() operation will block on) and a callback delegate that external tasks can use to invoke this async when they’re done with their work. Here’s my implementation of Select:
public class Select {
//Tasks will invoke this async via the CompletionCallback delegate when they’re done
async ProcessCompleted( object result );
private TaskCompletionCallback completionCallback;
public Select() {
//Set up the CompletionCallback member. Tasks will invoke //this callback (and thus signal the ProcessCompleted async) //when they're done with their work. completionCallback = new TaskCompletionCallback( this.ProcessCompleted ); }
public TaskCompletionCallback CompletionCallback { get{ return this.completionCallback; } }
//The WaitOne() operation will block until the ProcessCompleted async //gets signaled. Effectively, it will unblock after the first task //completes – the other tasks will still complete, but the thread //waiting on WaitOne() will get unblocked once the first task is done.
public object WaitOne() & ProcessCompleted( object result ) { Console.Out.WriteLine( "Done: " + result.ToString()); Console.Out.WriteLine( "" );
return result; } }
Here’s how it’s used in practice:
Random r = new Random(); Task task0 = new Task( r.Next( 10 ) ); Task task1 = new Task( r.Next( 10 ) );
Console.Out.WriteLine( "Waiting for one of two processes to finish via Select" );
Select @select = new Select(); StringBuilder sb = new StringBuilder();
task0.Execute( sb, @select.CompletionCallback ); task1.Execute( sb, @select.CompletionCallback );
//This will unblock once either task0 or task1 is done, //depending on which one completes first.
StringBuilder results = (StringBuilder) @select.WaitOne();
It’s possible to extend the pattern exhibited by Select into a more general WaitForN. Instead of returning once one task completes, we wait until a specified number of tasks complete. To do this, we add another async – TaskCompleted(). The handler for the async keeps track of how many outstanding taks are done, and if it reaches a threshold value it singles the completion of the whole process by posting a message to the ProcessCompleted async. Here’s my implementation of WaitForN:
public class WaitForN {
//This signals that all N tasks are done async ProcessCompleted( object result );
//This signals that a single task has completed async TaskCompleted( object result );
async syncRoot();
private TaskCompletionCallback completionCallback;
private int n; private int maxN;
public WaitForN( int n ) { this.n = 0; this.maxN = n;
completionCallback = new TaskCompletionCallback( this.TaskCompleted ); //Need to prime the lock with a message, so the first call //to the locked statements doesn’t deadlock syncRoot(); }
public TaskCompletionCallback CompletionCallback { get{ return this.completionCallback; } }
//This blocks until ProcessCompleted fires public object WaitN() & ProcessCompleted( object result ) { return result; }
//This is a completely asynchronous chord. It gets called //on a new thread whenever a message gets posted to the TaskCompleted async. //It’s locked via syncRoot() to guarantee only one thread can be in this function //at a time. when TaskCompleted( object result ) & syncRoot() { this.n++; Console.Out.WriteLine( n + " tasks completed." );
|