Monday, August 28, 2006
One question I get a lot when teaching the VSTS course is "How can I move tasks or bugs from our existing system into TFS?"

The answer is quite simple: write a program that utilizes the Team Systems Object Model.

The Object Model is documented partially in the VSIP SDK -- if you intend to work with it, I highly recommend you go and grab that SDK from Microsoft, but here is a simple example of creating a bug in the first Team Project available on the server named TFSServer (you can imagine the rest which uses ADO.NET to pull your existing tracking tickets from whatever system you have):

   1:  using System;
   2:  using System.Collections;
   3:  using System.Collections.Generic;
   4:  using System.Text;
   5:  using Microsoft.TeamFoundation.Client;
   6:  using Microsoft.TeamFoundation.WorkItemTracking.Client;
   7:   
   8:  namespace EnterWorkItems
   9:  {
  10:      class Program
  11:      {
  12:          static void Main(string[] args)
  13:          {
  14:              TeamFoundationServer tfs = new TeamFoundationServer("TFSServer");
  15:              WorkItemStore wis = (WorkItemStore) tfs.GetService(typeof(WorkItemStore));
  16:   
  17:              Project teamProject = wis.Projects[0];
  18:              foreach (WorkItemType wit in teamProject.WorkItemTypes)
  19:                  Console.WriteLine(wit.Name);
  20:   
  21:              WorkItemCollection wic = wis.Query("Project='Test' AND Type='Bug'");
  22:              foreach (WorkItem wiEntry in wic)
  23:              {
  24:              }
  25:   
  26:              WorkItemType witBug = teamProject.WorkItemTypes["Bug"];
  27:              if (witBug != null)
  28:              {
  29:                  Console.WriteLine("Adding new bug to Team Project {0}", teamProject.Name);
  30:   
  31:                  WorkItem wi = new WorkItem(witBug);
  32:                  wi.Description = "This is a sample bug which was added through the object model";
  33:                  wi.Reason = "New";
  34:                  wi.Title = "You have Bugs! [Ding]";
  35:   
  36:                  wi.Save();
  37:                  Console.WriteLine("Added Work Item # {0} created by {1}", wi.Id, wi.CreatedBy);
  38:              }
  39:          }
  40:      }
  41:  }


Breaking this down a bit, the first main piece of code is the connection to the Team Server itself. This is accomplished on line 14 by creating a new TeamFoundationServer object. An optional constructor allows for credentials to be provided, otherwise it logs on as the current principle.

Next, we retrieve the WorkItemStore. Almost everything in the object model is accessed through the IServiceProvider interface which provides a GetService method where you pass in the System.Type object you want to work with. This is a nice versioning technique that is utilized in many other Microsoft technologies as well.

With the WorkItemStore, you can then query work item type definitions, one of which is necessary to create a WorkItem. You fill in the details for the Work Item you want to create and call the Save method (line 36) to commit the changes to the TFS store. The WorkItem id will then be valid and could be added to the existing bug tracking system as a forward link to the new information if you wanted.
posted on 8/28/2006 5:45:13 AM (Central Standard Time, UTC-06:00)  #   
 Monday, July 17, 2006
I was having lunch with an associate a while back and he mentioned a need to move a work item from one team project to another. While there isn't any direct support for this from the Team Explorer interface, I figured it couldn't be too hard to manipulate the underlying database and achieve the results - it's just SQL Server right? I hadn't actually looked at the schema mind you, I'm an optimist.

It wasn't quite as simple as I thought - it felt like the database had been designed by the security group and intentionally obscured for privacy. The TFS database is fairly generic (so that it can add arbitrary items into the schema for custom project types) and has been somewhat secured to protect the code itself - the stored procedures are encrypted and SQL Profiler doesn't give much information on what's happening.

So, I spent a week or so looking at the schema for the Work Item system and a lot of trial and error. End result is that I got a simple application to move work items around. This application does a couple of other things as well - lists projects, active web services, etc. I was mostly playing with the schema and trying to figure things out. It's just a console application but it does the job.

Here's the code, use at your own risk (a.k.a. if it screws up your system, I can't help you). The source for the simple test project is included so you can see what was done and incorporate it into your own code base if you like.

The program is fairly easy to use - a binary is included with the release. Issuing the command with no parameters generates a simple "Help" page --

C:\Work\TfsCmd>tfscmd
TfsCmd [command] [/t tfsserver] [/u user] [/p password] [params]
ListProjects - Lists the active projects on the TF server.
TfsCmd ListProjects
ListServices - Displays the web services exposed by the TF server.
TfsCmd ListWebServices
WQL - Execute a WorkItem Query.
TfsCmd Wql [query]
MoveWorkItem - Moves a work item from one Team Project to another
TfsCmd MoveWorkItem [id] [ProjectName]

Executing a command will use your logged on user/password (domain credentials) unless you supply a /u and /p parameter. For example, running the ListProjects command generates something like:

C:\Work\TfsCmd>tfscmd ListProjects
Project Name: Test
Status: WellFormed
Guid: 8c5b175b-b0ef-46bb-9a9e-dde05bb145ac
Process Template: MSF for Agile Software Development - v4.0
Defined Properties: MSPROJ

Listing the web services just hits the underlying database to get the information:

C:\Work\TfsCmd>tfscmd ListServices
BuildStoreService = http://(local):8080/Build/v1.0/BuildStore.asmx
BuildControllerService = http://(local):8080/Build/v1.0/BuildController.
IBISEnablement = http://(local):8080/Build/v1.0/Integration.asmx
LinkingProviderService = http://(local):8080/Build/v1.0/Integration.asmx
IProjectMaintenance = http://(local):8080/Build/v1.0/Integration.asmx
PublishTestResultsBuildService = http://(local):8080/Build/v1.0/PublishT tsBuildService.asmx

Moving work items is fairly easy - just provide the work item id (the numeric identifier) and the new project name - this must match an existing project in your TFS system. The tool will attempt to match up the iteration to a value in the new project - if it cannot, it will default to the first iteration available.


C:\Work\TfsCmd>tfscmd MoveWorkItem 1 "Mark's New Project"
Work Item #1: "Create Project Definition" is currently located in project "Test Project (Iteration 0)"
Moving work item to "Mark's New Project (Iteration 0)"

If you have the TFS client open, then you should shut it down and reopen it before modifying the moved work item - the client appears to cache bits of information and I had some issues with moved items if I didn't clear the cache by closing the work item window and reopening it.

Have fun!
posted on 7/17/2006 12:05:53 PM (Central Standard Time, UTC-06:00)  #   
 Monday, June 19, 2006
Recording telephone conversations with TAPI
posted on 6/19/2006 9:41:45 AM (Central Standard Time, UTC-06:00)  #   
 Monday, May 01, 2006

A colleague of mine, Kev Jones, has posted some information on using a detached SQL Server database for driving VSTS unit tests which works great if you need a full blown SQL implementation.  However, if all you want is a simple data feed, you can also use an Excel file, and as it turns out, it's pretty easy to do.

Let's start by stealing Kev's sample, hopefully he won't mind -- say I have a starship class with a FirePhotonTorpedo method:

public class Enterprise
{
   private int torpedosLeft = 10;

   public int FirePhotonTorpedo(int count)
   {
      if (torpedosLeft < count)
         throw new ArgumentException("count");

      torpedosLeft -= count;
      // Instruct bridge officer to "Fire!"
      return torpedosLeft;
   }
}

The equivalent unit test might look something like:

[TestMethod]
public
void TestFirePhotonTorpedo()
{
   Enterprise target = new Enterprise();
   int torpedoCount = 5;
   int expected = 5;

   int actual = target.FirePhotonTorpedo(torpedoCount);
   Assert.AreEqual(expected, actual, "FirePhotonTorpedo did not return the expected value.");
}

This code just tests a specific case -- I would also need to write other unit tests for edge cases and exceptional cases.  I can do this several different ways I could do this:

1) Write a unit test for each specific case passing each value and testing the expected result.
2) Put all the tests into this one unit test function -- asserting each validation as we go.
3) Pull the input and expected out of a database and run them through the function.

This last step, as Kev details can be done from a SQL database - either one reachable by all the people who might run the unit tests, or as his blog shows from a .MDF file you check in with the project and then locally attach prior to running the unit tests.  Hit the link above for the details on that. 

So, to make an Excel data-driven test, the first step is to create an Excel document with columns for each of our pieces of data.  My sample Excel document has the following columns:

ID A unique number identifying the row so we can use the Random data driven test
torpedoCount The input for our unit test
expected The expected result coming out of the function
shouldFail Whether the function will throw an exception.

I can then enter a single test on each row of the given worksheet.  Here's a screen shot --

You can create multiple "tables" by adding additional sheets to the worksheet.  Each sheet can be named as appropriate; I'm naming this one "TorpedoData".

Now, I need to add the appropriate attribute to my test case to indicate that it should pull the data from a data source and run the method once per row found.  The key is that any ADO.NET data source can be used.  Here I will specify my Excel file which is named "UnitTests.xls" and indicate that the table itself is a particular sheet within the Excel document "TorpedoData":

[TestMethod]
[DeploymentItem("Tests.xls")] // Copies the file to the deployment directory
[DataSource(
"System.Data.OleDb", // The provider
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source='Tests.xls';Persist Security Info=False;Extended Properties='Excel 8.0'",
"TorpedoData$",      // The table name, in this case, the sheet name with a '$' appended.
DataAccessMethod.Sequential)] 

public
void TestFirePhotonTorpedo()
{

}

I also need to update the test case itself to use the data -- we do that through the TestContext.DataRow property that gives us access to the current row of data from our data source:

public void TestFirePhotonTorpedo()
{

   Enterprise target = new Enterprise();
  
int torpedoCount = Convert.ToInt32(TestContext.DataRow["torpedoCount"]);
   int expected = Convert.ToInt32(TestContext.DataRow["expected"]);
   bool shouldFail = (bool)TestContext.DataRow["shouldFail"];

   TestContext.WriteLine("Running test with TorpedoCount={0}, ExpectedCount={1}, ShouldFail={2}",
            torpedoCount, expected, shouldFail);

   try
   {
      int actual = target.FireTorpedos(torpedoCount);
      Assert.AreEqual(expected, actual, "FireTorpedos did not return the expected value.");
   }
   catch (Exception ex)
   {
      if (!shouldFail)
         Assert.Fail(string.Format("FireTorpedo threw exception {0}", ex.Message));
   }
}

So, here we will grab the data from the current row, converting it as necessary to the appropriate types, output a test line just to prove that we executed the method more than once and then run the test.  The test will compare the result with the expected database result and output a failure if they aren't the same.  If an exception is thrown, then the shouldFail must be true or that will be considered a failure.

This approach allows me to run through different scenarios very easily and I can just store the Excel worksheet right with my unit tests - make sure it's deployed to the target deployment directory (either through a DeploymentItem attribute, or through the test run configuration).  The size of my table here is about 14K, compared to the equivalent .MDF file which is over 2M for the same data.  If I didn't want to hardcode the filenames and such, I can also use an app.config file for the unit test and put the information there - just as Kev details.

Cool stuff indeed.

posted on 5/1/2006 10:55:40 AM (Central Standard Time, UTC-06:00)  #   
 Thursday, April 20, 2006

Minor, but breaking update for ATAPI.NET (version number has changed).  It was pointed out to me that the assembly wasn't very VB.NET friendly in that it didn't show any events at the TapiManager level and you couldn't use the UI to hook it all up.  That's fixed and all the line-level events are also exposed at the TapiManager level for those who want to use VB.NET with the 2.0 wrapper.

 

posted on 4/20/2006 8:01:49 AM (Central Standard Time, UTC-06:00)  #   
 Tuesday, April 04, 2006

Recently someone informed me that the TAPI wrappers (ITAPI3 and ATAPI) do not appear to function properly on the Win64 platform.  It throws an exception that says:

"Could not load file or assembly 'ITapi3, Version=1.0.0.3, Culture=neutral, PublicKeyToken=36377d9f6f1f4883' or one of its dependencies. An attempt was made to load a program with an incorrect format."

Now, as most know, .NET code compiles to an Intermediate Language (IL) which is a bytecode that is translated to the appropriate processor-specific instructions at runtime by the just-in-time (JIT) compiler.  The type of code that is generated is determined by the version of the CLR that is loaded.

When a .NET application starts, mscoree.dll is responsible for determining the proper version of the CLR to start for the process.  This is done by looking at the application manifest to see which version of .NET the application was compiled for (1.0, 1.1, or 2.0) and mscoree will suck in the appropriate assemblies and DLLs for that specific version, or in some cases, upgrade it silently to a newer version.

On Win64, the picture is a little more complex because we have to consider the platform as well.  There are actually two 2.x versions of .NET on Win64 - a 32-bit version (which is the same as the one running on Win32) and a 64-bit version which has been optimized for the 64-bit platform and generates 64-bit JITted code.  When an assembly starts on a Win64 platform, the mscoree.dll will look at not just the version, but also the platform flag which is coded into the manifest.  We can see this flag using ILDASM:

The .corflags above tells the loader that this particular assembly requires the 32-bit CLR, in other words, that we have a dependency on a 32-bit resource such as a COM object or platform DLL.  By default, the flag will be set to 0x0000001 (ILONLY) indicating no dependency (VS.NET refers to this as "AnyCpu" in the platform flag settings) and, on a Win64 machine, the assembly will be loaded under the 64-bit CLR.  With it set as above, on a Win64 machine it would be loaded into the 32-bit CLR.  For those who are interested in this aspect, there is a tool in the SDK (CORFLAGS.EXE) that will let you manipulate this flag and force an ILONLY assembly to be loaded as 32-bit.

VS.NET allows you to change the platform type on an assembly-by-assembly basis through the project settings, Build Tab:

When loading, this platform CLR determination appears to be, as with the CLR versioning, based solely on the assembly that starts the process.  The loader doesn't scan the dependencies and determine that one of the required assemblies is marked as '"x86" and will simply fail the process when that assembly eventually gets loaded.

So, if I have an assembly that requires 32-bit execution (such as ATAPI.NET or ITAPI3 that depend on the 32-bit TAPI subsystem and COM objects), but my starting assembly is marked as "AnyCpu", then the loader will start it under Win64 as a 64-bit process and when it tries to initialize ATAPI.NET or ITAPI3, it will fail the AppDomain with an exception.  Marking ATAPI.NET and ITAPI3 as "x86" (which they are by the way) won't help in this case -- you must mark your application as 32-bit.

This really is a bummer and, while I'm not surprise the CLR loader doesn't do it, I am surprised that VS.NET doesn't force the .EXE to be "x86" when it sees a dependency that requires it.

The wrappers do function under Win64, but only if the starting application has the platform target marked as "x86".

posted on 4/4/2006 9:39:12 AM (Central Standard Time, UTC-06:00)  #   
 Thursday, March 30, 2006

I have updated ITAPI3 to fix a couple of reported bugs -- the TE_FILETERMINAL event wasn't always being raised and the TCall.GenerateCustomTone didn't work properly.  Both of these issues are fixed in the latest drop.  Enjoy!

posted on 3/30/2006 5:16:09 PM (Central Standard Time, UTC-06:00)  #   
 Friday, March 24, 2006

Great news!  Rotor is an incredible tool for learning about how and why the CLR works the way it does.

For more info -- http://blogs.msdn.com/jasonz/archive/2006/03/23/559581.aspx

 

posted on 3/24/2006 8:38:31 AM (Central Standard Time, UTC-06:00)  #   
 Wednesday, March 22, 2006

A few months ago, I ran into a really weird bug with the System.Net.NetworkInformation.Ping class.  I was using it to monitor the network connectivity to a server and as my program ran, it appeared to leak handles.  I was calling Dispose (as I should) but it was still appeared to be leaking handles when called over and over. 

Here's the basic code I was running (simplified for this example):

Ping icmp = new Ping();
icmp.PingCompleted += delegate(object sender, PingCompletedEventArgs e)
   {
      PingReply reply = e.Reply;
      Console.WriteLine("Address: {0} - {1}", url, reply.RoundtripTime);
      icmp.Dispose();
  // Get rid of resources
   };

icmp.SendAsync(url, 100);

// Continue doing work

The code looked ok to me, so I started looking a little deeper.  In reality, I found that if I waited long enough, the GC process was cleaning up the unmanaged handles using the SafeHandle class, but I was confused because Dispose should have done that for me. When I looked at the ping class using Reflector, the problem became obvious and it's a warning to anyone building components that need to free up non-memory related resources.

In order to integrate with the Windows Forms and ASP.NET designer, the Ping component extends System.ComponentModel.Component.  This provides the design-time integration with Visual Studio .NET and it also provides some basic plumbing that used to cleanup the component - specifically it implements IDisposable for you and provides a nice virtual Dispose method which you are supposed to override and free your resources.  The code follows Microsoft's IDisposable pattern exactly - providing the IDisposable.Dispose method which delegates to an internal virtual void Dispose(bool isDisposing) method which is the method you should override.

The Ping component uses an internal socket and the ICMP W2K support under the covers to do its work and this socket needs to be cleaned up.  So, the author implemented IDisposable to indicate this -

public class Ping : Component, IDisposable
{
   private void InternalDispose()
   {
      if (!disposed)
      {
         // Cleanup socket and/or ICMP handle resources..
      }
   }

   IDisposable.Dispose()
   {
      this.InternalDispose();
   }
}

Note how the author used an explicit interface implementation for the Dispose method.  This means we will need to cast the object to an IDisposable interface in order to call the method (something I'm not doing above).  In fact, it won't even show up as a callable method inside VS.NET.  There is nothing wrong with this implementation (except of course it doesn't follow Microsoft's guidelines) until you add in the derivation from Component.  If we add it's implementation into the mix, and expand it out I get something like:

public class Ping : Component, IDisposable
{
   private void InternalDispose()
   {
      if (!disposed)
      {
         // Cleanup socket and/or ICMP handle resources..
      }
   }

   IDisposable.Dispose()
   {
      this.InternalDispose();
   }

   public void Dispose()
   {
      this.Dispose(true);
      GC.SupressFinalize(this);
   }

   protected virtual void Dispose(bool disposing)
   {
         // Remove object from component container
   }
}

See the problem?  I now have a public Dispose method available directly - and intellisense will show that.  This is the one I was calling my code when my async call was finished.  The problem is that it wasn't actually disposing the unmanaged resources - it was running the Component.Dispose code.

I need to back up and make another point on this.  I wouldn't have even seen the problem if I had been doing things synchronously.  In that case, I would have likely doing a using statement:

using (Ping icmp = new Ping())
{
   PingReply reply = icmp.Send(url, 100);
   if (reply.Status == IPStatus.Success)
   {
      Console.WriteLine("Address: {0} - {1}", url, reply.RoundtripTime);
   }
}

In this case, the C# compiler is nice enough to dispose of the object for me - and it does it by casting the object to IDisposable.  So, the generated code would really look like:

Ping icmp = new Ping();
try
{
   PingReply reply = icmp.Send(url, 100);
   if (reply.Status == IPStatus.Success)
   {
      Console.WriteLine("Address: {0} - {1}", url, reply.RoundtripTime);
   }
}
finally
{
   ((IDisposable)icmp).Dispose();
}

So, this would end up calling the correct implementation.  It was only because I was calling Dispose directly that I had a problem.  If the author had followed the IDisposable guidelines, this problem would have been found immediately because the C# compiler would have spit out a warning that public void Dispose() is hiding a base class implementation - cluing the author in that they need to hook into the base class implementation.  So, how did this happen?  My guess is that originally the Ping class didn't extend Component.  That derivation was added later in order to provide for design-time support.

If you are building components yourself, don't fall into this trap!  Always, always, always use Microsoft's stated guidelines - here's a simple example for those not familiar with it:

public class MyResource: IDisposable
{
   // Track whether Dispose has been called.
   protected bool disposed = false;

   // Implement IDisposable.
   // Do not make this method virtual.
   // A derived class should not be able to override this method.
   public void Dispose()
   {
      Dispose(true);
      // This object will be cleaned up by the Dispose method.
      // Therefore, you should call GC.SupressFinalize to
      // take this object off the finalization queue
      // and prevent finalization code for this object
      // from executing a second time.
      GC.SuppressFinalize(this);
   }

   // Dispose(bool disposing) executes in two distinct scenarios.
   // If disposing equals true, the method has been called directly
   // or indirectly by a user's code. Managed and unmanaged resources
   // can be disposed.
   // If disposing equals false, the method has been called by the
   // runtime from inside the finalizer and you should not reference
   // other objects. Only unmanaged resources can be disposed.
   protected virtual void Dispose(bool disposing)
   {
      // Check to see if Dispose has already been called.
      if(!this.disposed)
      {
         // If disposing equals true, dispose all managed
         // and unmanaged resources.
         if(disposing)
         {
           // Dispose all managed resources here.
         }
        
         // Call the appropriate methods to clean up
         // unmanaged resources here.
      }
      disposed = true;
   }
}

There's a bunch more information on this topic is section 9.3 of the Framework Design Guidelines - a must read for anyone building class libraries or reusable components.


 

posted on 3/22/2006 1:22:23 PM (Central Standard Time, UTC-06:00)  #   
 Monday, March 20, 2006

One issue that's always a struggle with building reusable components is managing asynchronous operations.  The problem is that depending on the type of application that is going to use the component, the thread used for callbacks and events may or may not be important.  For example, with Console based applications, callbacks on different threads might be ok - at least as long as the application itself ensures thread safety.  But, with a Windows Forms application, threading is critical - you are not allowed to touch UI controls from any thread other than the main one and so we end up with the Control.BeginInvoke logic in each of our callbacks which sucks.

Enter the Synchronization Context - a new feature of .NET 2.0.  Here's how it works:

You derive your component from System.ComponentModel.Component.Component and provide your typical asynchronous function -- in this example, we will build a PiCalculator:

public class PiCalculator : Component
{
   public PiCalculator();
   public object CalculatePi(int digits, object stateData);
   public void CancelAsync(object asyncTask);
}

Here, what we've done is create a new component with a default constructor and a method called CalculatePi which takes the number of digits, an optional piece of state data and returns an object.  With this component, I'd like to have multiple outstanding asynchronous operations and so I need some way to track each one to identify it when it completes, and also to cancel it if it runs too long.

Microsoft's general pattern for this is to use the stateData parameter and have the application pass in some unique value to identify the request. 

This is an ok way to do it, but it puts several restrictions on our implementation:

1) The state data must be supplied
2) The state data must be unique for each operation

In addition, there's the possibility of a race condition if I re-use the state data after canceling an operation.  So, to avoid all of these issues, I have the component return the task identifier as part of the request - this is the object return value coming back from CalculatePi.  I can then use that object to identify the results, and I can pass it into the component's CancelAsync method to cancel a pending request.

So, how do I get my results?  Through a delegate callback of course!  I need to create a delegate signature that uses it.  The general EventHandler callback signature is: void Method(object sender, EventArgs e) so, we'll use a derivative of this as our delegate:

public delegate void PiCalculationCompletedEventHandler(object sender, PiCalculationEventArgs e);

Then we'll create the class which will be used to report the results.  The class is pretty simple - just a data holder really:

public class PiCalculationEventArgs : EventArgs
{
   private int _digits;
   private string _value;
   private bool _canceled;
   private object _stateData;
   private object _taskId;

   public object TaskId
   {
      get { return _taskId; }
   }

   public object State
   {
      get { return _stateData; }
   }

   public bool Canceled
   {
      get { return _canceled; }
   }

   public int Digits
   {
      
get { return _digits; }
   
}

   public string Result
   {
      get { return _value; }
   }

   internal PiCalculationEventArgs(object taskId, int digits, string value, object stateData, bool canceled)
   {
      _digits = digits;
      _value = value;
      _canceled = canceled;
      _stateData = stateData;
      _taskId = taskId;
   }
}

Now we can implement out component.  First, we'll add a Completed event:

public event PiCalculationCompletedEventHandler CalculationComplete;

This will be used by the application to hook into the results of our component's calculation.  The next part is the key to all of this - AsyncOperation.  The AsyncOperation class is new to 2.0 and provides the facility to perform callbacks on the appropriate thread.  Essentially, it acts as a callback mediator - detecting the type of thread it was created on and then providing the appropriate marshaling code for it's internal delegate.  You utilize the callback through a new delegate type -- SendOrPostCallback.  We tie our callback to this delegate type and it will marshal us to the correct threading model.  We can then execute our own internal CalculationComplete event.  Here's the basic skeleton:  First, we will create an internal contained class which will be used to track the request.  This will actually be the object type we return from the CalculatePi method:

class AsyncStateData
{
   public AsyncOperation asyncOperation;
   public volatile bool canceled = false;
   public volatile bool running = true;

   public AsyncStateData(object stateData) 
   { 
      asyncOperation =
AsyncOperationManager.CreateOperation(stateData); 
   }
}

Notice the call to AsyncOperationManager.CreateOperation?  This is where we create our AsyncOperation class and this factory creator is the context detector.  We will have an AsyncOperation for each event we intend to fire back into the client - in our case one for each Pi calculation.  But, I could also create a single instance for the client as well - this is actually what is done in the ITapi3 component to allow it to be integrated onto a Windows Form even though events are being received on a background thread.  The Tapi events are always fired on the appropriate thread - the background one for non-WinForms apps and the UI thread for WinForms apps.

Next, we will create our internal callback - this is the callback that will actually be fired internally and then call the real application event, so we'll hook that up with an anonymous delegate in the constructor of our component:

private SendOrPostCallback completionMethodDelegate;

public PiCalculator()
{
   completionMethodDelegate =
delegate(object evt)
   {
      
// Called on the synchronization thread.
      
if (CalculationComplete != null)
         CalculationComplete(
this, (PiCalculationEventArgs)evt);
   };
}

Now, let's implement our CalculatePi method - it's pretty simple, we'll use an asynch delegate to our internal calculator, invoke it and return the AsyncStateData structure we create to identify each task submitted to the component.  Then the calculation will be performed on a thread pool thread and we'll callback to the client when it is finished.

public object CalculatePi(int digits, object stateData)
{
   PiDelegate piDel = InternalCalculatePi;
   
AsyncStateData asyncData = new AsyncStateData(stateData);
   piDel.BeginInvoke(digits, asyncData,
delegate(IAsyncResult ar) { piDel.EndInvoke(ar); }, null);
   
return asyncData;
}

CancelAsync is pretty simple too -- it will simply set the Canceled flag of the request:

public void CancelAsync(object asyncTask)
{
   
AsyncStateData asyncData = asyncTask as AsyncStateData;
   
if (asyncData != null && asyncData.running == true)
      asyncData.canceled =
true;
}

Now, we need to implement our PiCalculator.  We're going to cheese out here and just "pretend" to calculate Pi since that wasn't really the point of this component :-)  We'll define a delegate to use for the asynch execution and then implement our function:

private delegate void PiDelegate(int digits, AsyncStateData asyncData);
private void InternalCalculatePi(int digits, AsyncStateData asyncData)
{
   string PI_DIGITS = "3.141592637309238932482438234724782347234";
   
if (digits > PI_DIGITS.Length - 2)
      digits = PI_DIGITS.Length - 2;

   // This would be a real calculator here..
   
int completedDigits = 0;
   
for (; !asyncData.canceled && completedDigits < digits; completedDigits++)
   {
      
Thread.Sleep(1000);
   }

   asyncData.running = false;
   
string data = PI_DIGITS.Substring(0, completedDigits + 2);
   asyncData.asyncOperation.PostOperationCompleted(
      
completionMethodDelegate,
      
new PiCalculationEventArgs(asyncData, digits, data, asyncData.asyncOperation.UserSuppliedState, asyncData.canceled));
}

The key to the callback is the invocation of the PostOperationCompleted method from the AsyncOperation instance we created.  It is what calls our handler which will in turn call the client.  Once PostOperationCompleted is called, no further work may be done with the AsyncOperation.  So, it's not appropriate for progress reporting - instead you can use PostOperateration for that which allows for multiple calls. 

Now, when using this component, we can simply call it as expected:

static void Main(string[] args)
{
   ArrayList taskIds = new ArrayList();
   
   PiCalculator piCalc = new PiCalculator();
   piCalc.CalculationComplete +=
delegate(object sender, PiCalculationEventArgs e)
   {
      Console.WriteLine("[{0}] {1} = {2}, Canceled = {3}"
         
Thread.CurrentThread.ManagedThreadId, e.Digits, e.Result, e.Canceled);
   }         

   
foreach (string s in args)
   {
      taskIds.Add(piCalc.CalculatePi(
Convert.ToInt32(s), s));
   }

   Console.WriteLine("Waiting for results .. press ENTER to cancel.");
   
Console.ReadLine();

   foreach (object task in taskIds)
   {
      piCalc.CancelAsync(task);
   }

   Console.WriteLine("Press ENTER to terminate");
   
Console.ReadLine();
}

Here's the output:

Waiting for results .. press ENTER to cancel.
[3] 10 = 3.1415926373, Canceled = False
[4] 11 = 3.14159263730, Canceled = False
[5] 14 = 3.14159263730923, Canceled = False
[5] 15 = 3.141592637309238, Canceled = False
[5] 17 = 3.14159263730923893, Canceled = False

Press ENTER to terminate
[5] 20 = 3.1415926373092389324, Canceled = True

Not real exciting right?  Notice the thread id in the [brackets] is different for some of the callbacks.  This indicates we are getting called back on different threads - certainly not the main thread which is waiting for console input.  The cool part of this component is that I can use it exactly the same way in a Windows Forms application!  So, I don't have to know that the callback is on a different thread!  I don't have to worry about doing a BeginInvoke or Invoke to get back to the UI thread.  So, here's an example WinForm application:

public partial class CalcPiTestForm : Form
{
   ArrayList _pendingTasks = new ArrayList();

   public CalcPiTestForm()
   {
      InitializeComponent();
   }

   private void CalcPiTestForm_Load(object sender, EventArgs e)
   {
      piCalculator1.CalculationComplete += new UserMath.PiCalculationCompletedEventHandler(piCalculator1_CalculationComplete);
   }

   void piCalculator1_CalculationComplete(object sender, UserMath.PiCalculationEventArgs e)
   {
      lock(_pendingTasks)
      {
         _pendingTasks.Remove(e.TaskId);
      }

      // No need to do BeginInvoke here!!
      listBox1.Items.Add(string.Format("[{0}] {1} = {2}, Canceled = {3}",
         System.Threading.Thread.CurrentThread.ManagedThreadId, e.Digits, e.Result, e.Canceled));
   }

   private void btnCalculate_Click(object sender, EventArgs e)
   {
      if (maskedTextBox1.Text.Length > 0)
      {
         lock(_pendingTasks)
            _pendingTasks.Add(piCalculator1.CalculatePi(Convert.ToInt32(maskedTextBox1.Text)));
      }
   }

   private void btnCancel_Click(object sender, EventArgs e)
   {
      lock(_pendingTasks)
      {
         foreach (object task in _pendingTasks)
            piCalculator1.CancelAsync(task);
      }
   }
}

Notice the output here -- we haven't done anything special with the component's callbacks, but now the callback is always on thread [1].  This is the magic of AsyncOperation and synchronization contexts.  Now go out there and write some thread-aware components!  If you'd like this entire sample, here is the project: http://www.julmar.com/samples/asyncop.zip

posted on 3/20/2006 4:42:45 PM (Central Standard Time, UTC-06:00)  #