2010-08-16

Various Implementations of Asynchronous Background Tasks

This is going to be a long blog post, because there's plenty of ground to cover. Executive summary: there are lots of ways to do background tasks in .NET, but use the new Task class if possible because it's the best. :D

Many user interface applications find that they need to support some kind of an asynchronous "background" task. The exact requirements vary, but most applications need some kind of ability to kick off an operation that will run without blocking the user interface; and have that operation report back to the user interface thread when completed.

Common Requirements

Not all applications need all of these, but some common requirements are:

  • Results. Usually, the purpose of the asynchronous operation is to determine some value or set of values that is then used to update the state of the program.
  • Errors. If there is an exception during the background processing, then it's nice to have that exception preserved, including its original call stack.
  • Progress. In addition to just updating program state upon completion (or error), it's often useful to incrementally report progress.
  • Cancellation. For long-running operations (especially of the CPU-intensive variety), one should include some kind of cancellation mechanism. If possible, the background operation should have a way to detect when cancellation has been requested and respond properly.
  • Nesting. A commonly-overlooked requirement is the ability to nest background operations. This is not needed for simple UI-layer background operations, but it becomes more important when designing business-layer background operations.
  • Synchronization. Usually there is some UI that must be updated when a background task reports progress, completes with a result, completes with an error, or is cancelled.

Tasks

The best overall approach is the new Task Parallel Library introduced in .NET 4.0. The following requirements are fully supported:

  • Results. The Task<TResult> class allows the natural returning of the result. The result is retrieved by reading Task<TResult>.Result.
  • Errors. Any exceptions thrown by a task are rethrown when the task is observed. The call stack is properly preserved.
  • Cancellation. .NET 4.0 includes a unified cancellation framework that provides exhaustive cancellation support.
  • Nesting. Tasks can be nested if desired; child tasks properly propogate any exceptions upward, and parent tasks may optionally propogate cancellation downward. Nesting is not automatic, so this ability should be exposed by any business-layer API that is Task-based.
  • Synchronization. Tasks introduce a very flexible model of synchronization by separating the actual operation from how it is scheduled. Synchronization with the user interface is only slightly awkward; a user interface task scheduler can be retrieved by calling TaskScheduler.FromCurrentSynchronizationContext while on the UI thread. This scheduler can then be used to schedule a task continuation to marshal the result, error, or cancellation update to the UI thread.

Progress reporting is a bit complex for tasks:

  • Progress. The most flexible way to report progress from a task is to create another task (to update the UI), schedule it to the UI thread, and wait for it to complete. There is a ProgressReporter wrapper class on this blog which helps simplify the code.

BackgroundWorker

Before .NET 4.0 was released, BackgroundWorker was the de-facto standard. It supports most of the requirements:

  • Results. Supporting a result is slightly awkward; the DoWork delegate has to set the DoWorkEventArgs.Result property of its argument. This value is then passed to the RunWorkerCompleted delegate, as the RunWorkerCompletedEventArgs.Result property.
  • Errors. Exceptions thrown by DoWork are caught and passed to the RunWorkerCompleted delgate, as the RunWorkerCompletedEventArgs.Error property. That exception object does include the correct call stack; however, if the RunWorkerCompleteEventArgs.Result property is accessed when the operation completed with an error, then the original exception is wrapped in a TargetInvocationException, so the outer exception no longer carries the correct call stack.
  • Progress. Any BackgroundWorker whose WorkerSupportsProgress property is true may report progress. The DoWork delegate invokes ReportProgress, which causes the ProgressChanged event to fire. Progress reporting is always asynchronous, so DoWork will continue to run before the ProgressChanged event actually executes.
  • Cancellation. Any BackgroundWorker whose WorkerSupportsCancellation property is true may be cancelled. The cancelling thread first calls BackgroundWorker.CancelAsync. This causes the BackgroundWorker.CancellationPending property to become true. The DoWork delegate should monitor that property (checking it on a regular basis), and set DoWorkEventArgs.Cancel to true and return if the operation is cancelled. The RunWorkerCompleted delegate detects a cancelled result by checking RunWorkerCompletedEventArgs.Cancelled.
  • Synchronization. The biggest benefit of BackgroundWorker is its support for automatic synchronization. The ProgressChanged and RunWorkerCompleted events are synchronized to the SynchronizationContext that was in place when RunWorkerAsync was called. In most situations, RunWorkerAsync is called from a UI thread, and so the ProgressChanged and RunWorkerCompleted events are invoked on the UI thread.

BackgroundWorker does have one rather significant drawback. It works perfectly for less complex systems, but does not nest easily.

  • Nesting. BackgroundWorker's problem with nesting is because the DoWork delegate is not given a SynchronizationContext in which to run. Because of this, any time RunWorkerAsync is called from DoWork, the ProgressChanged and RunWorkerCompleted events on the child BackgroundWorker are not synchronized to the UI thread (or to the parent BackgroundWorker). This can be solved one of two ways:
    • If the child BackgroundWorker should synchronize to the UI, then the parent BackgroundWorker can manually install the UI SynchronizationContext in its DoWork delegate by calling SynchronizationContext.SetSynchronizationContext.
    • If the child BackgroundWorker should synchronize to the parent BackgroundWorker (not the UI), then the parent BackgroundWorker can run a synchronization loop using an object like Nito.Async.ActionDispatcher. Alternatively, the parent BackgroundWorker could be replaced entirely by a Nito.Async.ActionThread.

Delegate.BeginInvoke

Every delegate in .NET supports asynchronous invocation. This is a lower-level technique that does not require a separate object (e.g., Task or BackgroundWorker) to define an asynchronous operation. Because it is at a lower level, it supports fewer of the standard requirements:

  • Results. The result of the delegate may be retrieved by calling Delegate.EndInvoke, even if the asynchronous delegate has already completed.
  • Errors. Any exception thrown by the delegate is preserved and rethrown by Delegate.EndInvoke, properly preserving the call stack.

This lower-level approach does not cleanly support these requirements:

  • Progress. A delegate must be designed to support progress reporting; one way to do this is to have the method take another delegate as one of its arguments and invoke that delegate to report progress.
  • Cancellation. There is no built-in cancellation support, but a delegate may regularly check for a cancellation signal (e.g., a volatile bool or ManualResetEvent).
  • Nesting. Delegates may of course asynchronously invoke other delegates; however, there is no concept of "parent" and "child" asynchronous delegates. Propogation of errors is automatic, but propogation of cancellation is not.
  • Synchronization. There is no automatic synchronization for asynchronous delegates. There are two common solutions:
    • Use the AsyncOperation and AsyncOperationManager classes. These types provide a thin wrapper around SynchronizationContext, allowing for simple (asynchronous) synchronization of progress and completion. The disadvantage of these classes is that they do not support nesting. [Note: BackgroundWorker just uses these classes with an asynchronous delegate, so if you need synchronization, it's usually just best to use Tasks or BackgroundWorker]
    • Use the SynchronizationContext class directly. The synchronization code is a bit more complex, but it is possible to support nesting.

ThreadPool.QueueUserWorkItem

One of the lowest-level approaches is to queue the work directly to the ThreadPool. Unfortunately, this approach does not support any of the requirements directly; every requirement needs a fair amount of work:

  • Results. The delegate passed to ThreadPool.QueueUserWorkItem cannot return a value. To return a result, one must either use a child object of an argument (similar to BackgroundWorker) or pass a lambda expression bound to a variable holding the return value.
  • Errors. If a delegate queued to the ThreadPool allows an exception to propogate, then the entire process is killed. If any errors are possible, then they should be wrapped in a try...catch and the exception object "returned" to the calling thread (either using a child object of an argument, or using a bound variable of a lambda expression). The exception could be rethrown with the correct stack trace by calling PrepareForRethrow from the Rx library.

The other requirements have the same problems (and mitigating solutions) as the Delegate.BeginInvoke approach above.

Thread

Of course, one obvious approach is to place a background operation in its own thread. This is often a sub-optimal solution, since the ThreadPool is designed to handle varying loads. There is almost never a need to manually create background Thread objects. However, many programmers naturally turn to the Thread class as an obvious solution.

Just like the ThreadPool.QueueUserWorkItem approach, the manual Thread approach does not support any of the requirements out of the box. Manual threads have the same problems (and mitigating solutions) as the ones listed under ThreadPool.QueueUserWorkItem. In addition, manual Thread objects almost always are less efficient than the built-in ThreadPool.

2010-08-05

Using Rx for Stream Encoding and Decoding

Still on my Rx kick...

The Rx team had a two-part series last month demonstrating one way to use Rx on the server: asynchronous Stream and asynchronous StreamReader. In the asynchronous StreamReader example, they punt on the decoding issue, instead demonstrating how to split the text into lines.

As it turns out, decoding (and encoding) are quite simple in Rx. The code below should be clear to anyone who's been following the Rx team blog posts:

using System;
using System.Linq;
using System.Text;

/// <summary>
/// Observable extension methods that encode and decode streams.
/// </summary>
public static class EncodingObservables
{
  /// <summary>
  /// Takes a "chunked" sequence of characters and converts it to a "chunked" sequence of bytes using the specified encoding.
  /// </summary>
  /// <param name="source">The "chunked" sequence of characters.</param>
  /// <param name="encoding">The encoding used to translate the sequence of characters to a sequence of bytes.</param>
  /// <returns>The "chunked" sequence of bytes.</returns>
  public static IObservable<byte[]> Encode(this IObservable<char[]> source, Encoding encoding)
  {
    return Observable.CreateWithDisposable<byte[]>(observer =>
    {
      var encoder = encoding.GetEncoder();

      return source.Subscribe(
        data =>
        {
          try
          {
            var ret = new byte[encoder.GetByteCount(data, 0, data.Length, false)];
            encoder.GetBytes(data, 0, data.Length, ret, 0, false);
            if (ret.Length != 0)
            {
              observer.OnNext(ret);
            }
          }
          catch (EncoderFallbackException ex)
          {
            observer.OnError(ex);
          }
        },
        observer.OnError,
        () =>
        {
          try
          {
            var ret = new byte[encoder.GetByteCount(new char[0], 0, 0, true)];
            encoder.GetBytes(new char[0], 0, 0, ret, 0, true);
            if (ret.Length != 0)
            {
              observer.OnNext(ret);
            }

            observer.OnCompleted();
          }
          catch (EncoderFallbackException ex)
          {
            observer.OnError(ex);
          }
        });
    });
  }

  /// <summary>
  /// Takes a "chunked" sequence of bytes and converts it to a "chunked" sequence of characters using the specified encoding.
  /// </summary>
  /// <param name="source">The "chunked" sequence of bytes.</param>
  /// <param name="encoding">The encoding used to translate the sequence of bytes to a sequence of characters.</param>
  /// <returns>The "chunked" sequence of characters.</returns>
  public static IObservable<char[]> Decode(this IObservable<byte[]> source, Encoding encoding)
  {
    return Observable.CreateWithDisposable<char[]>(observer =>
    {
      var decoder = encoding.GetDecoder();

      return source.Subscribe(
        data =>
        {
          try
          {
            var ret = new char[decoder.GetCharCount(data, 0, data.Length, false)];
            decoder.GetChars(data, 0, data.Length, ret, 0, false);
            if (ret.Length != 0)
            {
              observer.OnNext(ret);
            }
          }
          catch (EncoderFallbackException ex)
          {
            observer.OnError(ex);
          }
        },
        observer.OnError,
        () =>
        {
          try
          {
            var ret = new char[decoder.GetCharCount(new byte[0], 0, 0, true)];
            decoder.GetChars(new byte[0], 0, 0, ret, 0, true);
            if (ret.Length != 0)
            {
              observer.OnNext(ret);
            }

            observer.OnCompleted();
          }
          catch (EncoderFallbackException ex)
          {
            observer.OnError(ex);
          }
        });
    });
  }
}

This class defines two operators (Encode and Decode) which can be used like this:

[TestClass]
public class EncodingObservablesUnitTests
{
  [TestMethod]
  public void MSDNEncoderSample()
  {
    var chars = new[]
    {
      new[] { '\u0023' }, // #
      new[] { '\u0025' }, // %
      new[] { '\u03a0' }, // Pi
      new[] { '\u03a3' } // Sigma
    };

    var result = chars.ToObservable(Scheduler.ThreadPool)
                      .Encode(Encoding.UTF7)
                      .ToEnumerable()
                      .SelectMany(x => x)
                      .ToArray();
    Assert.IsTrue(result.SequenceEqual(new byte[] { 43, 65, 67, 77, 65, 74, 81, 79, 103, 65, 54, 77, 45 }));
  }

  [TestMethod]
  public void MSDNEncoderGetBytesSample()
  {
    var chars = new[]
    {
      new[] { '\u0023' }, // #
      new[] { '\u0025' }, // %
      new[] { '\u03a0' }, // Pi
      new[] { '\u03a3' } // Sigma
    };

    var result = chars.ToObservable(Scheduler.ThreadPool)
                      .Encode(Encoding.Unicode)
                      .ToEnumerable()
                      .SelectMany(x => x)
                      .ToArray();

    Assert.IsTrue(result.SequenceEqual(new byte[] { 35, 0, 37, 0, 160, 3, 163, 3 }));
  }

  [TestMethod]
  public void MSDNDecoderSample()
  {
    var bytes = new[]
    {
      new byte[] { 0x20, 0x23, 0xe2 },
      new byte[] { 0x98, 0xa3 },
    };

    var result = bytes.ToObservable(Scheduler.ThreadPool)
                      .Decode(Encoding.UTF8)
                      .ToEnumerable()
                      .SelectMany(x => x)
                      .ToArray();

    Assert.IsTrue(result.SequenceEqual(new[] { '\u0020', '\u0023', '\u2623' }));
  }
}

Note that I've defined the Encode and Decode operators as working on "chunks" of data. As such, they don't really "fit in" with most LINQ and Rx operators, which work on individual data elements. However, this approach makes sense any time there's buffered reading going on. The Encode and Decode operators here will work fine with the Rx team's example AsyncRead operator.

Also note that these simple Encode and Decode operators will not treat encoding preambles in any special way (including Unicode byte order marks). They won't prefix the encoded output with a preamble, nor will they detect any preambles when decoding.

2010-08-02

A Reminder about Asynchronous FileStreams

Still on my Rx kick...

The Rx team published a great blog post regarding using Rx on the server with asynchronous Streams. When doing this, you do need to make sure that the FileStream is actually asynchronous. (I believe the Rx team is fully aware of this caveat, but neglected to mention it in their blog post because it's not directly relevant to Rx).

To create a FileStream that is asynchronous, one must either use the constructor that takes an isAsync boolean paramter (passing true), or use the constructor that takes the FileOptions parameter (passing a value including FileOptions.Asynchronous). Some of the static methods on the File class also take a FileOptions parameter, so these can also be used to create an asynchronous FileStream.

A FileStream that is constructed any other way is not asynchronous. If the asynchronous APIs (such as BeginRead, BeginWrite, etc.) are used on a non-asynchronous FileStream, it will use a ThreadPool thread to "fake" asynchronous operations. Using Rx to wrap the Begin/End methods in this case only provides the illusion of asynchronous operations.

Using Rx to access a non-asynchronous FileStream is counterproductive, burning a ThreadPool thread. However, using Rx to access an asynchronous FileStream provides all the benefits of true asynchronous I/O.

2010-07-31

Asynchronous Contexts in Rx

Yesterday I (finally) wrote my first real-world code using Rx. Like many others, I've played around with various aspects of Rx, but until last night these were all just throwaway experiments. It turns out that in my very first real-world use of Rx, I had to implement an old concept: asynchronous contexts.

There have been some great resources released about Rx recently. Most notably, the Rx hands-on lab (direct link to PDF), which is the closest thing to an Rx tutorial in existence. The Rx team followed up later this month with a two-part series on using Rx on the server: asynchronous Stream and asynchronous StreamReader. These blog posts are great examples of how to think when approaching a problem with Rx in hand.

This week, I had a business need to create a "search" form. The form is very simple: the user types something in a TextBox, and we populate a ListView with matching objects. It's sort of like a whole form devoted to AutoComplete. The actual "matching" function could be run asynchronously, so this problem ended up almost exactly like the dictionary lookup in the Rx hands-on lab document.

The one big difference is that the "matching" function will return its results incrementally (it's actually an IEnumerable<T>), and I'd like to display the results incrementally as they are found. In contrast, the dictionary lookup in the Rx hands-on lab example returns all of its results at once.

Here's the first brush of the code:

// Listen for the user typing.
var searchCommands = Observable.FromEvent<EventArgs>(this.textBoxSearch, "TextChanged")
  .Select(x => this.textBoxSearch.Text)
  .Throttle(TimeSpan.FromMilliseconds(200)) // For fast typists.
  .DistinctUntilChanged() // Only pass along the event if the actual text changed.
  .ObserveOn(this) // Marshal to UI thread.
  .Merge(Observable.Return(string.Empty)) // Start by searching an empty string.
  .Do(_ =>
  {
    // Update UI each time we get a new search request.
    this.listViewResults.Items.Clear();
    this.labelStatus.Text = "Searching...";
  });

// Define how we do searches.
Func<string, IObservable<T>> performSearch = searchString => this.matchProvider.Lookup(searchString)
  .ToObservable(Scheduler.ThreadPool) // Do the iteration on a ThreadPool thread.
  .ObserveOn(this); // Marshal to the UI thread.

// Each time a search is requested, cancel any existing searches and start the new one.
this.searchAction =
  searchCommands
  .Select(searchString =>
    performSearch(searchString) // Do the search.
    .Do(_ => { },
      () =>
      {
        // Update UI when the search is done.
        this.labelStatus.Text = "Done!";
      }))
  .Switch() // Cancel existing searches when a new search starts.
  .Subscribe(response => this.listViewResults.Items.Add(this.toListViewItem(response)));

The first chunk of the code is almost identical to the first chunk of the Rx hands-on lab code. The only difference is that I use ObserveOn(this) and Do() to clear out any previous search results when a new search starts (the hands-on lab clears previous search results when a search completes). I also do a Merge() with an empty string, which causes all results to be returned as soon as the form is loaded.

The second chunk of code defines how searches are performed. The "matchProvider" object just returns an IEnumerable<T> for a given search string. This enumerable is iterated on a ThreadPool thread, and the results are marshalled to the UI thread. This is similar to the asynchronous web service used by the Rx hands-on lab, except that it produces its results incrementally instead of all at once.

The third part of the code uses the Switch() operator to cancel old searches and start new ones as they are ready. A label is updated to notify the user when a search completes. All results from the combined searches are added to the ListView as they arrive. There is no need to marshal to the UI thread first, because both of the observable sources in this combination have already been marshalled to the UI thread.

The Need for an Asynchronous Context

There's a rather subtle race condition in the code above. Observable sequences can get tricky whenever they change threads, and that is happening a couple of times here. The first one is not really obvious: Throttle() transfers control to a ThreadPool thread because of its timer. The other one is obvious: we're converting an IEnumerable<T> to an observable using Scheduler.ThreadPool. Both of these sequences do get marshalled back to the UI thread and combined using Switch(), and that's actually where the problem comes in.

According to an authoritative post on the Rx forums, when subscriptions are disposed they may not stop immediately. At first this seems like a design flaw, but it actually makes perfect sense. Believe me - I've done enough asynchronous work to know how complicated it would be to have all subscription disposals stop their observables immediately.

In short, it's possible to have a former search complete (and update the UI displaying "Done!") after a newer search starts (and updates the UI displaying "Searching..."). The Rx hands-on lab does not have this problem because they only marshal to the UI thread (and display the results) when the lookup has completed.

Conceptually, this is the same problem that I discussed in one of my first blog posts: an asynchronous operation can't always be reliably cancelled. In this case, the solution is to introduce an asynchronous callback context and have the operation actively check its context before executing. If the callback is synchronized before checking the callback context, then it knows whether or not it is cancelled (without causing another race condition).

To solve this problem in Rx, we'll use an asynchronous context (dropping the "callback" moniker, since it doesn't really apply). The concept is the same: asynchronous events copy the current value of the context (while they are synchronized), then go off and do whatever they do asynchronously, and finally check their saved context against the current value of the context (after they are re-synchronized).

Note that asynchronous contexts in Rx need to be attached to each element in the observable. Logically, each observable element is an event.

Using the Asynchronous Context

This code uses an asynchronous context. The simplest context is just an Object instance, which can be easily compared for equality and is guaranteed unique from any other context.

// Our asynchronous context.
object context = null;

// Listen for the user typing.
var searchCommands = Observable.FromEvent<EventArgs>(this.textBoxSearch, "TextChanged")
  .Select(x => this.textBoxSearch.Text)
  .Throttle(TimeSpan.FromMilliseconds(200)) // For fast typists.
  .DistinctUntilChanged() // Only pass along the event if the actual text changed.
  .ObserveOn(this) // Marshal to UI thread.
  .Merge(Observable.Return(string.Empty)) // Start by searching an empty string.
  .Do(_ =>
  {
    // Change the context to prevent any future updates from old observables.
    context = new object();

    // Update UI each time we get a new search request.
    this.listViewResults.Items.Clear();
    this.labelStatus.Text = "Searching...";
  })
  .Select(searchString => new { context, searchString }); // Attach context to each search string.

// Define how we do searches.
Func<string, IObservable<T>> performSearch = searchString => this.matchProvider.Lookup(searchString)
  .ToObservable(Scheduler.ThreadPool) // Do the iteration on a ThreadPool thread.
  .ObserveOn(this); // Marshal to the UI thread.

// Each time a search is requested, cancel any existing searches and start the new one.
// Propogate the context to each search result.
this.searchAction =
  searchCommands
  .Select(request =>
    performSearch(request.searchString) // Start searching.
    .Select(result => new { request.context, result }) // Propogate the context to each search result.
    .Do(_ => { },
      () =>
      {
        // Check the context before handling the result.
        if (request.context == context)
        {
          // Update UI when the search is done.
          this.labelStatus.Text = "Done!";
        }
      }))
  .Switch()
  .Subscribe(
    response =>
    {
      // Check the context before handling the result.
      if (response.context == context)
      {
        this.listViewResults.Items.Add(this.toListViewItem(response.result));
      }
    });

The changes in this code all have to do with the asynchronous context. The local "context" variable always refers to the currently valid context (all other contexts are, by definition, invalid). When a new user search request is detected, we create a new context for the request, and we "bind" the context to the search request using an anonymous projection.

The second block of code (defining how we perform a search) is the same. The search results are treated a bit differently, though: we "bind" each search result to the same context associated with the search request. Also, when the search is completed, the request's bound context is verified against the current context before updating the UI.

Finally, the bound context for each response is verified against the current context before updating the UI. Remember that each response's context is copied from their associated requests's context, so they remain valid as long as their request is the most recent one.

Note that all context-based actions (setting the current context when starting a request, binding the current context to the observable elements, and verifying the bound contexts against the current context) are all done on the UI thread. Synchronizing context actions is a requirement for asynchronous contexts, to avoid race conditions.

A Reusable Solution

I'm playing around with a few classes that make asynchronous contexts a little easier to use. Observable elements bound to a context are placed into a structure similar to Timestamped<T> (which binds observable elements to a timestamp), and there are special binding and verification operators. The actual AsynchronousContext type also includes thread checking to ensure that it is used in a synchronized fashion.

However, I'm just not pleased with how usable it is. I'll continue playing with it over the next week or so, and if I can find a good solution, I'll post it here and put it into Nito.Async. Suggestions are welcome. :)

2010-07-16

ReSharper and the Obscure CTS Corner Case

Like several other people, I collect tricky code snippets for fun. Today's image is courtesy of the Common Type System (part of the CLR). As such, it's not so much an artifact of the C# language as it is an artifact of the floating point standard.

Interestingly, the current version of ReSharper recommended a code transformation that is wrong.

Don't get me wrong; ReSharper is a great tool. This is the first time I've seen it make a mistake, and it's an obscure corner case. ReSharper did make another questionable recommendation a few weeks ago, and I felt the C# standard wasn't clear on the subject. However, Eric Lippert did confirm that ReSharper's refactoring was correct that time.

2010-06-30

Review of Bill Wagner's Effective C# (2nd ed), Part 2

Continuing my long and drawn-out review of Effective C#, this post takes a look at items 6-10.

Item 6: Understand the Relationships Among the Many Different Concepts of Equality

+ This is often a confusing topic for newcomers, and Bill explains it pretty well. He clearly distinguishes reference and value equality.

+ Correct recommendations on when and how to define equality for user-defined types.

+ Correctly discusses handling equality in the context of a type hierarchy. [Note: the class hierarchy example is only the simple case where objects of different types are always different. This does not handle the (uncommon) case where there is a sub-hierarchy where objects of different types can be equal.]

- Minor technical error: this section references the "IStructuralEquality" interface which had its name changed prior to the 4.0 release and is now called IStructuralEquatable.

- The only mention of overriding GetHashCode is buried in the text and not even a comment is included in the examples for overriding Equals.

Item 7: Understand the Pitfalls of GetHashCode()

- Repeatedly states that the result of GetHashCode must be equal if the two objects are equivalent as defined by operator==. This is incorrect; GetHashCode must be kept in sync with Object.Equals, not operator==.

+ Correctly explains efficiency problems with default GetHashCode implementations.

- Attempts to enforce more strict requirements on GetHashCode - specifically, that it can only be based on immutable fields. The actual requirements are only that the "key" field values do not change while the object's hash is being used.

- Incorrectly states that only immutable types can have a correct and efficient implementation of GetHashCode.

+ Pushes readers towards immutable value types. Even though GetHashCode doesn't require them, they are easier to work with.

Item 8: Prefer Query Syntax to Loops

- Assumes that query syntax is always cleaner than loops.

+ Points out the "composable API" benefit of query syntax.

Item 9: Avoid Conversion Operators in Your APIs

+ I agree completely, and would include operator overloading in the same cautionary advice.

Item 10: Use Optional Parameters to Minimize Method Overloads

+ Clearly explains all of the binary compatibility issues with optional parameters and default values.

2010-06-25

On A Lighter Note: SocketFlags.MaxIOVectorLength

Today I was just working along, minding my own business, when out of the blue my mind jumped back to something strange I had seen over a year ago. (Is anyone else insane like that, or is it just me?)

The seldom-used SocketFlags enumeration serves a dual purpose: it can represent flags passed to the Send or Receive operation, and it also represents flags passed back from the Send or Receive operation.

Reading through the enumeration values is pretty much straightforward: it's fairly obvious which ones are meant as "input" or "output" parameters, and what their meanings are. One value, however, is rather strange: MaxIOVectorLength, which (according to the MSDN documentation) "Provides a standard value for the number of WSABUF structures that are used to send and receive data."

That should give anyone pause. That value is clearly not a flag. It would make (a twisted sort of) sense if, by passing that flag, you could specify the maximum I/O vector length. But a quick look at the Send and Receive methods make it clear that this flag is not "enabling" some other parameter.

The fact is: this flag value should simply not exist. The value is real enough; it's defined in WinSock2.h as "MSG_MAXIOVLEN". However, it defines a limitation in the WinSock implementation, not a flag for Send or Recv.

Why do I find this amusing? Because someone, during the devlopment of the .NET framework, had to track down all the meanings of these flags. This person undoubtedly discovered that MSG_MAXIOVLEN was undocumented in its header file, and learned its meaning from someone else (likely someone responsible for the WinSock code). And in all of that research, that person never once noticed that this value was obviously not a flag? Not only that, but all of the reviewers reading this documentation never once realized how its description was completely different than all of the other descriptions!

This is a case of someone working too fast, and no one catching their fundamental mistake. The other flag values (which existed in WinSock.h with names like "MSG_OOB", "MSG_PEEK", and "MSG_DONTROUTE") had straightforward translations to SocketFlags, and MSG_MAXIOVLEN somehow got lumped in with them.

P.S. An interesting futher note: the person who put MaxIOVectorLength into SocketFlags correctly did not include a translation of MSG_INTERRUPT. The MSG_INTERRUPT flag was used to signal WinSock that the Send/Recv is being called in a hardware interrupt context (and therefore WinSock could not call other Windows methods). That was back in the 16-bit Windows days, and that flag is no longer used.

P.P.S. Bonus amusing fact: SocketFlags.MaxIOVectorLength has the same value as MSG_INTERRUPT. He, he, he... I just wonder what would happen if someone ever used it... :)