Welcome to MSDN Blogs Sign in | Join | Help

The Queued Tab:

image

So, as you can see not much has changed on the Queued tab of the build explorer since VSTS 2008, but if you look close, there are a few changes.

1. There is a new filter you can apply - “Only show builds requested by me”. This filter will limit the results to only those builds you directly caused by checking in or manually queuing it. This is really helpful when there are lots of builds for your team project and you only care about yours.

2. There is a new column on the left. If you hover over it, you see it’s the “Reason” column. This shows you what caused the build to start. In the picture above, you can see two builds. The one with the icon was triggered by a checkin and the other was manually queued.

3. All columns except the image columns are sizeable. They start off adjusted to their data, but you can change that.

 

The Completed Tab:

image

Likewise on the Completed tab, not much has changed. In fact, it has the same changes as the Queued tab – a new filter, a new column, and sizeable columns. They work the same here as on the Queued tab.

BuildDetailView-Log-InProgress

The picture above is of the Log View of the new Build Details View in Visual Studio Team System 2010. See my previous post on the Summary View for more information on the items above the words “Activity Log”. The log view can be seen by opening an in-progress build like the one shown above or by clicking the “View Log” link on a completed build.

Here are some things to notice about this view:

1. There are links (“Next Error” and “Next Warning”) to quickly jump to the first or next error or warning. The Log View can be quite long so this should help you find the errors more quickly. Of course, the error messages will also show up on the Summary view, so you may not need to come here at all.

2. The “Show Property Values” link will expand the log view even further and show you all the property values that were logged for the build activities. Because these values can greatly increase the size of the log, they are turned off by default.

3. On the right is a duration column. This shows you the duration of each build activity. If you are trying to speed up your builds, this information should help you determine what build activities are taking the longest. Note that the values roll up. So, parent duration values are approximately the sum of their children’s durations.

4. The data is presented in a hierarchy. This hierarchy maps perfectly to the build process template (more on that in a later post). This allows you to follow the path that the build took through the template and possibly correct problems with your custom templates. Note: showing the property values is important to understanding the flow.

5. The “play” icon in front of some of the lines indicates that those build activities are currently in progress. Another way to follow along as a build follows the process template logic. Note that a parent activity is considered in-progress if any of its children are in-progress.

6. Lastly, like the summary view, there is a slider in the bottom right corner that allows you to zoom in or out on the log view.

I hope this gives you some more insight into the 2010 release!

BuildDetailView-Summary-CompilationFailure

The picture above is of the new “Build Report” in TFS 2010. I prefer the term Build Details View, because it is not actually a TFS report object. The view has completely changed, so let me take you on a quick tour:

1. The first thing you may notice is that this is a failed build. The icon in the upper left corner notifies you of the status (in progress, succeeded, partially succeeded, or failed).

2. The next thing on that same line is the build number “Build HW_20090512.3” and the status is repeated as a word. Then comes the Build Quality. That’s right you can change the build quality right here on the build details view.

3. The links on the next line allow you to view the summary page (that’s the page we see above), view the log page (need another post for that one), open the drop folder, and delete the build.

4. The bar graph - now things really start getting interesting. This is a graph of the last 9 builds for the same definition. The small triangle denotes this build. The height of the bars show the relative time that the build took and the color indicates the status. You can also hover over the bar to get a tool tip with that information. This graph is really helpful when the build is in progress, because you can see how long the other builds took and guess how much longer this one will be :)

5. The rest of the information beside the graph includes who started the build (jpricket), what definition was triggered (Build HW), what changeset was built, how long the build took, what build controller was used, and when it was started. A lot of info in a small space!

6. Latest Activity – this section includes who last modified the build and when. It also lists any work items that were opened during the build. Notice the link! Clicking on the link will open the work item :)

7. Summary – This section contains a lot of information. The main purpose is to report the results of the build. It is divided by Configurations with another section at the end for any Errors or Warnings that may have happened outside of a configuration. Each configuration has four sections: errors and warnings, projects/solutions compiled, test results, and code coverage results. Notice the links! You can go to the actual build log for the configuration, or you can click on a particular error and go straight to the source line. (this actually downloads the file and opens the exact version that the Build Machine was attempting to build).

8. Impacted Tests – this section is completely new and allows you to see which tests were impacted by the changes that were checked in.

9. If there were test results, you would also have a link to them, just like you did in TFS 2008.

10. Associated changesets and work items will also show up in their own sections for a successful build. They also have links to open the Changeset details or the Work Item.

11. Oh, and that slider in the bottom right hand corner allows you to zoom in or out.

 

Hopefully this encourages you to download the Beta and start playing with it!

As I mentioned back in January, I created a collapsible section for use in a flow document. In my case, I was removing a treeView and replacing it with indented paragraphs in a flow document (see the previous post as to why). Some of the data that was now shown to the user all the time was rather useless in most cases. So, I had some suggestions to put back the tree. Well, I just didn’t want to lose my beautiful new FlowDocument, so I started experimenting. I noticed that a Section could contain paragraphs or any other type of Block. Naturally, I wondered how hard it would be to create a section that only showed its contained children when some property was changed. So, I wrote the code and this is how it turned out…

internal class CollapsibleSection : Section
{
    public CollapsibleSection()
    {
        CollapsibleBlocks = new List<Block>();
        Header = new Paragraph();

        m_expandCollapseToggleButton = new ToggleButton();
        m_expandCollapseToggleButton.Click += new RoutedEventHandler(ExpandCollapseToggleButton_Click);
        m_inlineUIContainer = new InlineUIContainer(m_expandCollapseToggleButton);
        m_inlineUIContainer.BaselineAlignment = BaselineAlignment.Center;
        m_inlineUIContainer.Cursor = Cursors.Arrow;

        Header.Inlines.Add(m_inlineUIContainer);
    }

    private void ExpandCollapseToggleButton_Click(object sender, RoutedEventArgs e)
    {
        Invalidate();
    }

    public Paragraph Header { get; private set; }
    public List<Block> CollapsibleBlocks { get; private set; }

    public bool IsCollapsed
    {
        get
        {
            return !(m_expandCollapseToggleButton.IsChecked ?? false);
        }
        set
        {
            m_expandCollapseToggleButton.IsChecked = !value;
        }
    }

    public void Invalidate()
    {
        Blocks.Clear();

        if (CollapsibleBlocks.Count == 0)
        {
            m_expandCollapseToggleButton.IsChecked = null;
        }

        Blocks.Add(Header);

        if (!IsCollapsed)
        {
            Blocks.AddRange(CollapsibleBlocks);
        }
    }

    private ToggleButton m_expandCollapseToggleButton;
    private InlineUIContainer m_inlineUIContainer;
}

As you can see, it was fairly easy to create this class. Not very much code at all. The class has a Header property that is the one paragraph that you see all the time and contains the toggle button. It also has a CollapsibleBlocks property that you can add any type of Block to. When the user clicks the button the IsCollapsed property is toggled and the Header and CollapsibleBlocks are used to modify the built-in Blocks property of the section.

I left out all the work I did after the fact to make the toggle button look like a triangle and rotate when clicked, but you should be able to figure that stuff out the same way I did, by looking at the way a TreeView works.

And here’s how I used the class:

   CollapsibleSection section = new CollapsibleSection();
   section.Margin = new Thickness(parentIndent, StandardPadding, 0, 0);
   section.IsCollapsed = false;

   . . .

   section.Header.Inlines.Add(header);

   . . .

   section.CollapsibleBlocks.Add(new Paragraph(new Run(“test paragraph”));

   section.Invalidate();

I don’t like having to invalidate the section after I have added everything, but I didn’t want to spend a lot of time trying to figure out a clever way around it.

I hope this gives you an example of just how extensible FlowDocuments are and why I like them so much!!!

As I promised, this is a post about what I learned while creating a flow document that had indented paragraphs. The hierarchy was dynamically built based on the data.

The first thing I noticed is that indenting a paragraph is very easy, just set the left margin of the paragraph and the entire paragraph will be indented. If you want just the first line of the paragraph indented, simply set the TextIndent property. In either case, the value is a double that represents WPF device indendependent units. If you want the first line to be outdenting, or you simply want a hanging indent. You have to set the left margin to the hanging indent value and then set the TextIndent to the negative of that value.

Another way to indent a paragraph is to add it to a section that has the left margin set. Sections don't have a TextIndent property, but a section can contain another section (unlike paragraphs). So, If you nested 3 sections that all had an indent of 20.0, the result would be that the inner most section would be indented 60.0 units from the left edge of the window. I didn't use this approach because I didn't have to. It was simple enough for me to simply indent each paragraph accordingly.

So, that's a lot of talking without any code or pictures. Here is some XAML to demonstrate indenting:

 <FlowDocumentScrollViewer>
     <FlowDocument>
         <Section>
          <Paragraph>
       This paragraph is not indented in any way and should wrap normally. This paragraph is not indented in any way and should wrap normally. This paragraph is not indented in any way and should wrap normally.
          </Paragraph>
      </Section>
         <Section>
          <Paragraph TextIndent="50">
       The first line of this paragraph is indented by 50 units, but the rest of the lines will not indent at all. The first line of this paragraph is indented by 50 units, but the rest of the lines will not indent at all.
          </Paragraph>
      </Section>
         <Section>
          <Paragraph TextIndent="-50" Margin="50,20,0,0">
       The first line of this paragraph is not indented, but the rest of the lines will be indented by 50 units. The first line of this paragraph is not indented, but the rest of the lines will be indented by 50 units.
          </Paragraph>
      </Section>
         <Section>
          <Paragraph Margin="50,20,0,0">
       This paragraph is indented by 50 units, but the section is not indented at all. This paragraph is indented by 50 units, but the section is not indented at all. This paragraph is indented by 50 units, but the section is not indented at all.
          </Paragraph>
          <Paragraph Margin="100,0,0,0">
       This paragraph is indented by 100 units, but the section is not indented at all. This paragraph is indented by 100 units, but the section is not indented at all. This paragraph is indented by 100 units, but the section is not indented at all.
          </Paragraph>
      </Section>
         <Section Margin="50,20,0,0">
          <Paragraph Margin="0,0,0,0">
       This paragraph is not indented, but the section is indented by 50 units. This paragraph is not indented, but the section is indented by 50 units. This paragraph is not indented, but the section is indented by 50 units.
          </Paragraph>
          <Paragraph Margin="50,0,0,0">
       This paragraph is indented by 50 units, but the section is also indented by 50 units. This paragraph is indented by 50 units, but the section is also indented by 50 units. This paragraph is indented by 50 units, but the section is also indented by 50 units.
          </Paragraph>
      </Section>
         <Section Margin="50,20,0,0">
               <Section Margin="50,0,0,0">
              <Paragraph Margin="0,0,0,0">
           This paragraph is not indented, but it is in a nested section that is indented by 50 units and the outer section is also indented by 50 units - that makes 100 units total for this paragraph.
              </Paragraph>
          </Section>
      </Section>
  </FlowDocument>
 </FlowDocumentScrollViewer>

As I learn more and more about WPF, I am continually amazed at how much I don't know. I recently discovered FlowDocuments (look here for more info on the basics of FlowDocuments) and how wonderful they are. Okay, I knew about them a little, but I ignored them because I didn't think you could do very much with them other than present text to the user. Well how wrong I was!

Why? 

I found my self using lots and lots of textblocks to display some information to the user. I was struggling with several limitations of textblocks: how they wrap depends on the container, databinding part of the text is not possible without databinding all of it, and there is no way to allow the user to select the text for copying to the clipboard. These limitations caused me to write lots of code to work around the issues and provide the user experience that I wanted. I had dozens of ValueConverters to format the bound data into the correct strings and even more code and XAML to get the text blocks to wrap appropriately.

What? 

A colleague suggested that FlowDocuments might provide some answers, So dug into them and discovered a world of solutions to the problems that I faced. Flow Documents are kind of like HTML. In fact you can find lots of sites that compare the two if you search for it. Here are the features that drew me in and forced me to rewrite my entire interface to use FlowDocuments:

  1. Selection and copying to the clipboard support is natural and free.
  2. The memory foot print compared to all the controls that I was using before was a huge improvement and likewise the performance was better as well (although I didn't measure it).
  3. For displaying text there is nothing better. You can easily indent, bold, align, wrap, etc.
  4. Hyperlinks work properly only inside a FlowDocument.
  5. If you use a FlowDocumentScrollViewer to display your flow document (this is what I used), you get Scrolling, Find, Print, and Zoom capabilities.

The one downside is that like textblocks there is not a simple way to bind data to the document. But for a read-only view of data, it is perfect.

When?

Based on my experience with FlowDocument so far, I would recommend using them anytime the number of textblocks outnumber the other controls on your window. You can also embed a flow document within another container control like a Grid. But I wouldn't recommend that unless you really need it.

How?

I will write more posts on how I am using flowdocuments and the special things I have learned, but here is a quick synopsis of how I used them in this particular case:

I replaced the TreeView that I had bound to my hierarchical data with a recursive method that added indented paragraphs to a section. After it's done, I simply add the section to the flowdocument. Indenting a paragraph is a little tricky, but that's a later post. Of course, the tree is no longer collapsible, but I didn't really need it to be anyway. The indentation was all I needed. I created my own value converters that could convert by data into paragraphs. That allowed my recursive function to be very generic.

I used Figures to have a right justified column of numbers associated with some but not all of the paragraphs in my hierarchy. There's not much info about Figures so I will probably make that one of my first posts after this one.

Paragraphs can have borders, so I used that fact to add some nice horizontal lines to the output that lined up the numbers on the right with there headers and grouped some of the paragraphs together.

One of the last things I did was to add in a way for the user to add additional text to certain areas. In one instance I added a button that simply created paragraphs for some objects in the hierarchy that are skipped by default. In other areas, I created my own kind of expander that has the look of a tree view item and adds additional paragraphs below itself. I called this last one an CollapsibleSection because I derived it from Section. I will definitely have a post on that one.

 

We have had quite a few complaints about the fact that we delete the labels created during a build when the build is deleted. The reason this behavior was added was to keep from creating a ton of labels for CI builds. But some customers still want the labels to exist after the build has been deleted. Long term we will allow you to choose what gets deleted with the build. But for now, we have made a change that is available in Team Foundation Server 2008 SP1 that you should like.

If you want to always keep labels even though builds are being deleted by retention policies or manually, you can simply make a change on the Application Tier in the web.config file. Here are the details:

1) Locate the Team Foundation Web.Config file on the Application Tier.

2) Add a flag to the Web.config file to specify that labels should not be deleted. When this value is present, labels are not deleted from Source Control. The default behavior is still the same as RTM; labels get deleted with their corresponding builds. Here is an example of what that line in the web.config file would look like:
<add key="PreserveLabelsOnBuildDeletion" value="True"/>

3) Run IISReset to force the changes to be used.

I hope this helps those customers that use labels a lot. 

What is a stretching treeView?

Recently, I found the need to have a TreeView in my WPF application that was only a few levels deep. I didn't want a horizontal scrollbar to appear and I wanted the long text nodes to wrap. So, I invented the StretchingTreeView control.

Why couldn't I do this with a normal WPF TreeView?

Well, you can, but you have to replace the ControlTemplate for all the TreeViewItems. That's either a lot of XAML or a lot of code.

So, what exactly is the problem?

If you look at the default ControlTemplate for a TreeViewItem, you will see that it contains a grid with 3 columns. In the first column is the expander, in the second column is the Header of the TreeViewItem. And in the last column is nothing. The second column is set to a Width of Auto which means that it will size to its contents. The third column is set to a Width of Star which means it will grow and shrink with the width of the TreeView. No matter what you put in the Header of the TreeViewItem, it will never stretch to the right edge of the TreeView. And Since the Header column is set to Auto, it will never cause a TextBlock or anything else to wrap. Instead, if you turn off the horizontal scrollbar, your long text items simply get clipped at the right edge of the TreeView.

So, what's the code to fix it?

Well it was actually very simple. Once I found out that you can control what kind of Controls a TreeView creates for its items. By subclassing TreeView and overriding the methods GetContainerForItemOverride and IsItemItsOwnContainerOverride, you can control what types are created for your tree. This is especially important if you are databinding your tree to some hierarchy of objects and can't just create TreeViewItems directly. In any case here is the code...

    class StretchingTreeView : TreeView
    {
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new StretchingTreeViewItem();
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is StretchingTreeViewItem;
        }
    }

    class StretchingTreeViewItem : TreeViewItem
    {
        public StretchingTreeViewItem()
        {
            this.Loaded += new RoutedEventHandler(StretchingTreeViewItem_Loaded);
        }

        private void StretchingTreeViewItem_Loaded(object sender, RoutedEventArgs e)
        {
            // The purpose of this code is to stretch the Header Content all the way accross the TreeView. 
            if (this.VisualChildrenCount > 0)
            {
                Grid grid = this.GetVisualChild(0) as Grid;
                if (grid != null && grid.ColumnDefinitions.Count == 3)
                {
                    // Remove the middle column which is set to Auto and let it get replaced with the 
                    // last column that is set to Star.
                    grid.ColumnDefinitions.RemoveAt(1);
                }
            }
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new StretchingTreeViewItem();
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is StretchingTreeViewItem;
        }
    }

Basically, all I do is create my own TreeViewItems that wait until they are loaded and then fix the Grid by removing the middle column. I don't like the fix, because it seems like a hack, but it's hardly any code. And that I like.

I hope this helps somebody out there!

In an previous post (long long ago), I described some scenarios around why you would want to subscribe to change the work item tracking subscription to the build completion event. The purpose there was to help users "correct" the subscription that came out of the box. In this post, I would like to answer a question I got from a user about how to subscribe to the other build event - BuildStatusChangeEvent. The question was basically this: How do I get notified when the build quality changes from 'X' to 'Y'?

First, let me say that the name of this event is all wrong. It does not fire when the status of a build changes, but rather when the quality field of the build changes. In the future, it may do more, but not for now. The name aside, it is important to look at the structure of the xml that is sent when the event fires. It is actually very short. Here is an example:

<?xml version="1.0" encoding="utf-16"?><BuildStatusChangeEvent xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <TeamFoundationServerUrl>http://jpricket-test:8080</TeamFoundationServerUrl>
  <TeamProject>TestProj</TeamProject>
  <Title>TestProj Build CSharp_Tests_20080507.3 Quality Changed To Ready for Deployment</Title>
  <Id>CSharp_Tests_20080507.3</Id>
  <Url>http://jpricket-test:8080/Build/Build.aspx?artifactMoniker=9</Url>
  <TimeZone>Eastern Daylight Time</TimeZone>
  <TimeZoneOffset>-04:00:00</TimeZoneOffset>
  <ChangedTime>5/9/2008 3:05:33 PM</ChangedTime>
  <StatusChange>
    <FieldName>Quality</FieldName>
    <OldValue>Lab Test Passed</OldValue>
    <NewValue>Ready for Deployment</NewValue>
  </StatusChange>
  <ChangedBy>NORTHAMERICA\jpricket</ChangedBy>
</BuildStatusChangeEvent>

The important bit is under the StatusChange node which also makes it a little more interesting than the last post that I did. To filter this event in any useful way requires you to know a little more about XPath. Or at least how the "path" to the OldValue and NewValue fields are formed. It's actually pretty simple. Ignore the root node BuildStatusChangeEvent and then use a slash between each node that you need to reference in the path. For example, if you want to filter on the NewValue field, you might want to use the filter expression 'StatusChange/NewValue'='Ready for Deployment'.

Finally, here are some scenarios that you might find yourself in:

  1. I want to be notified when a build is marked as "Released"
  2. I want to be notified when the build quality is removed (only applies to 2008 and above)
  3. I want to be notified when the build changes from "Rejected" to "Released"

And here are the filters that correspond to those scenarios:

  1. 'StatusChange/NewValue'='Released'
  2. 'StatusChange[count(NewValue) = 0]' <> null AND 'StatusChange[count(OldValue) > 0]' <> null
  3. 'StatusChange/OldValue'='Rejected' AND 'StatusChange/NewValue'='Released'

The first and last are exactly what you might expect. But the second filter was quite a challenge. It turns out that when OldValue or NewValue are null, they are not included in the XML at all. So, to see if NewValue was set to null, we have to get the StatusChange nodes that have a count of NewValue nodes equal to zero. If any StatusChange nodes are returned then that half of the filter is true. The other half is just the opposite. We want to make sure that the StatusChange node has at least 1 OldValue field. The syntax of the xPath query is a little hard to get right, but it does exactly what we want.

Hopefully, this will help you with this and other TFS Events! I have attached the code that I used to add these subscriptions to my server. It is very similar to the code in my previous post.

In Team Build 2005, the "definition" of the build was the same as the TfsBuild.proj file. However, in 2008, we changed this so that the TfsBuild.proj only held some of the information for the build. The rest of the information (like retention policies, workspace definition, and triggers) were all stored in the build database. 2008 even allows two different Build Definitions to share the same TfsBuild.proj file.

Unfortunately, due to this diversion of "definition" and source control file (TfsBuild.proj), users have found it difficult to easily get to the TfsBuild.proj file from the UI. Okay, you can find it, but you have to follow this procedure:

1) open the associated definition from team explorer

2) go to the project file tab

3) write down the server path

4) open Source Control Explorer

5) Navigate to the right server path

6) look for the file TfsBuild.proj

Well, in 2008 SP1, you don't have to do all that work any more. Simply right click on the definition in team explorer and choose "View Configuration Folder". This menu item will do all the of the above for you. In future versions, we may even dream of world without TfsBuild.proj files :o

For more information on all the new features in Team Foundation Server 2008 SP1, see bharry's blog about them.

(or Designing Your Workflow Activity Class Hierarchy)

One of the questions that we've had to consider when designing our Workflow Activity Class Hierarchy is whether it is valuable to have our own Activity base class (derived from Activity and inherited by all of our activity classes).

Our answer was "Yes". We decided that there were several good reasons to create our own Activity base class. Here are the reasons and some explanation.

  1. Implement the Asynchronous Activity pattern for all our activities - I described this pattern and the reasons that you want to do this in a previous post. Basically, the idea is that we want all our activities to do their work on a background thread so that they are cancelable.
  2. Shared properties - there were a few properties that we felt like we wanted on all our activities. I won't get into the specifics of them, but this is a reason to create any base class.
  3. Common tracking and logging - we wrote a little bit of code associated with Activity tracking and logging information about the workflow. We wanted to be consistent in the way that we logged about activities starting and stopping, so we put this code into the base class. We even have some generic code that logs all the Properties for our activities using the property collection that is inherited from the actual Activity class.

Our base activity class gives us all this but here's the rub...

There are actually two Workflow Activity base classes. If you want to create a Composite activity (one that contains other activities), you have to derive that class from CompositeActivity instead of Activity. This is extremely unfortunate for us, because there were several activities that we wanted to create that were composite activities. But this means that they couldn't derive from our other base class. So, we had to re-implement some of the base classes functionality in our Composite activity class. This is why when I am designing a tree structure, I try not to make a separate base class for the tree nodes and the leaf nodes. Because, it screws up inheritance, polymorphism and a few other fancy OO words I can't recall.

Keep this gotcha in mind when you start planning your Activity class hierarchy!

In my previous blog post (Creating an Asynchronous Workflow Activity), I explained why your custom activities should either be really fast or run asynchronously. But, I didn't give you a real world example of how to do this. In this post I provide an example of the pattern that my team uses when creating a custom activity.

Pattern:

1) Create a service that actually does the work

2) Create a custom activity that uses the service and has a Queue listener event handler to get a message back from the service when the work is done.

Instead of walking you through this one, I just want to present you with the code, commented as much as I could :)

    public class DoSomethingAsyncService : WorkflowRuntimeService
    {
        private Random m_random;

        public DoSomethingAsyncService()
        {
            m_random = new Random();
        }

        public void DoSomethingAsync(Guid instanceId, Guid queueId)
        {
            ThreadPool.QueueUserWorkItem(delegate(Object state)
                {
                    // Fake a call to a WebService (let's say it takes 10 seconds)
                    Console.WriteLine("Making webservice call.");
                    Thread.Sleep(10000);

                    // Create the QueueItem
                    QueueItem item = new QueueItem();
                    item.ReturnCode = m_random.Next(0, 10);
                    item.Message = String.Format("The return code is {0}.", item.ReturnCode);

                    try
                    {
                        // Now send this item to the appropriate queue
                        WorkflowInstance instance = Runtime.GetWorkflow(instanceId);
                        instance.EnqueueItem(queueId, item, null, null);
                    }
                    catch (InvalidOperationException)
                    {
                        // Catch any InvalidOperationExceptions that occur due to the 
                        // workflow having already completed, etc.
                    }
                });
        }
    }

    public class QueueItem
    {
        public int ReturnCode;
        public String Message;
    }
    public class DoSomethingAsyncActivity : Activity
    {
        private Guid m_queueId;

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            // Create my queue
            m_queueId = Guid.NewGuid();
            WorkflowQueuingService queuingService = (WorkflowQueuingService)executionContext.GetService(typeof(WorkflowQueuingService));
            WorkflowQueue queue = queuingService.CreateWorkflowQueue(m_queueId, true);

            // Hook the item available event. This event will be triggered when our
            // service enqueues an item.
            queue.QueueItemAvailable += new EventHandler<QueueEventArgs>(QueueItemAvailable);

            // Call the service to perform the async work.
            DoSomethingAsyncService service = (DoSomethingAsyncService)executionContext.GetService(typeof(DoSomethingAsyncService));
            service.DoSomethingAsync(WorkflowInstanceId, m_queueId);

            // Return Executing to let the workflow know that we are still working.
            return ActivityExecutionStatus.Executing;
        }

        private void QueueItemAvailable(object sender, QueueEventArgs e)
        {
            // The activity execution context is always passed in as the sender.
            ActivityExecutionContext context = sender as ActivityExecutionContext;

            // Checking for a null context just in case something really strange is happening.
            if (context != null)
            {
                // Get the queue service so we can get the Queue object.
                // Note: you cannot salt away the actual queue object, the Workflow runtime does not allow it.
                WorkflowQueuingService queuingService = context.GetService<WorkflowQueuingService>();

                // Make sure the queue exists before going any further.
                if (queuingService.Exists(m_queueId))
                {
                    WorkflowQueue queue = queuingService.GetWorkflowQueue(m_queueId);

                    // Dequeue the item that was queued by the service.
                    QueueItem item = queue.Dequeue() as QueueItem;
                    if (item != null)
                    {
                        // We only expect one thing to ever be put into our Queue;
                        // so, now we remove the queue.
                        queue.QueueItemAvailable -= QueueItemAvailable;
                        queuingService.DeleteWorkflowQueue(m_queueId);

                        // Finally inspect the results and do something with them.
                        Console.WriteLine(item.Message);

                        // Once we have finished our mission, we close this activity
                        context.CloseActivity();
                        return;
                    }
                }
            }

            // We should not get here if everything works as we expect.
            // So, throw an exception so the workflow will be terminated.
            throw new Exception("Something unexpected was placed in the Queue.");
        }
    }

Try creating a workflow that contains a Parallel activity and add this activity to both branches. If you really want to run activities in parallel, this is how it needs to be done.

Don't forget to add the service to the runtime!

Happy flowing!

What a silly topic for a blog post! Workflow already runs my whole activity tree on a another thread, I don't have to worry about being Async, Do I? Yes, you do. While it is true that each Workflow gets its own thread (in the default setup), that thread can be easily blocked by a long running or hung activity. Let's look at an example...

Create a Sequential Workflow Console application in Visual Studio with the following Activity tree:

Sequential Workflow

    WhileActivity (set the condition to true so it runs forever)

        CodeActivity (the code should just do a Console.WriteLine("Hello World")

So, now go to the Program.cs file or wherever the code is that Starts the workflow instance.

Add a Thead.Sleep(1000) after the instance.Start() call and then an instance.Terminate("kill workflow") to stop the workflow.

When you run the workflow, you should notice that you get a few "Hello World" lines and one "kill workflow" line. Here, terminate works just as you expect it to. But now let's change our workflow so that the while is done in the code activity...

Sequential Workflow

    CodeActivity (the code should contain while(true) Console.WriteLine("Hello World"))

When you run the workflow this time, it will never end :(

If you put a break point on the instance.Terminate line, it does get called. So, why doesn't the Workflow terminate? Well, like all other actions that happen in the workflow instance, the terminate action goes through a queue and waits for the background thread to dequeue it and do it. Unfortunately, the queue is only processed in between activities executing. So, the termination request remains in the workflow queue forever in our example.

If you are running complex workflows that may need to actually be stopped in the middle (like we do) then you need to avoid code activities that have loops and start writing asynchronous custom activities. So, how do we write a custom activity so that it is stoppable? The first thing you have to do is create a workflow runtime service that can do the actual work for the activity. This means for every custom activity you need a new custom service or at least a new method on your custom service. Here's a sample service:

    public class HelloWorldService : WorkflowRuntimeService
    {
        public void WriteHelloWorldForeverAsync()
        {
            ThreadPool.QueueUserWorkItem(delegate(Object state)
                {
                    while (true) Console.WriteLine("Hello World");
                });
        }
    }

This service will now do the work for us. But to use it, we need a custom activity. Here's the simplest custom activity you could make:

    public class WriteHelloWorldActivity : Activity
    {
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            HelloWorldService service = (HelloWorldService)executionContext.GetService(typeof(HelloWorldService));
            service.WriteHelloWorldForeverAsync();
            return ActivityExecutionStatus.Executing;
        }
    }

The key thing to note here is that the activity calls a method on the service that returns immediately after starting its background thread. The activity then returns Executing. This allows the workflow to dequeue items from the queue, but not move on to the next activity. Until this activity returns Closed, the next activity will not start.

Oops! before you try and run this you need to create your service, add it to the runtime and add your custom activity to the workflow. In Program.cs, add the following line right after you create the runtime service:

workflowRuntime.AddService(new HelloWorldService());

Then change the workflow to look like this:

Sequential Workflow

    WriteHelloWorldActivity

Now, you can run it and see that Terminate works again! Of course, in a real application you need more code than this, because you have to shut down the background thread when you get terminated, and you need to call back to your activity when the background work is finished! Tune in for my next post, where I will show you a real example of of an activity and service that do it all :)

In Visual Studio Team System 2008, the build machine downloads the files that it needs to launch MSBuild before it creates any workspaces. Because this initial "get" is before MSBuild is called, users don't get much say in what gets downloaded. By default, the build machine downloads all the files in the ConfigurationFolderPath of the build definition that is being built. You specify that path in the Build Definition dialog. Everthing at the root of that folder is then downloaded before the build starts.

The problem that some people have run into is that we only download the root of the folder. They have some assemblies or other specific build files that are nessesary in subfolders of that location. Luckily we thought of that. You can change the download parameters so that all subdirectories are downloaded as well. Be careful, however, if you specify to download everything, you will get everything under that folder that exists in version control. You cannot cloak anything out. Also note that this "fix" is per build machine, not build, and not build definition.

Here are the steps:

  1. Log into the build machine that you want to change (you probably need to be an admin on the box).
  2. Locate the TFSBuildService.exe.config file (most likely in "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies")
  3. open it in notepad or some other text editor
  4. Near the bottom (before the /appsettings closing tag) add the following line:
    • <add key="ConfigurationFolderRecursionType" value="Full" />
    • other values include OneLevel or None. OneLevel is the default.
  5. Save the file
  6. Restart the "Visual Studio Team Foundation Build Service" from the Services control panel applet.

That's it. I hope this helps!

In VS2008, you still have to run the Wizard to create a TfsBuild.proj file for your build definition. That wizard can be really slow to open. In fact, internally the wizard can seem to hang indefinitely. And if you have a large number of files checked into your Team Project you may experience the same thing. But, there is something you can do.

The Problem:

When the wizard opens it queries the version control server for all SLN and VSMDI files in the Team Project. The larger the Team Project the longer this takes.

The Solution:

In VS2008 we limited this exhaustive search to the workspace that is defined in the build definition. So, if you limit the number of files in the workspace, the query will run much faster.

Problem #2:

Because the definition dialog is where you define the workspace and launch the wizard, we don't force you to do one before the other. And the default workspace is the entire team project. That means that if a user tries to run the wizard first, they will run into the exact same slowness that was there in Version 1.

There is not a solution for this second problem except user training. Hopefully, you don't have too many people that are creating build definitions.

Happy Building!

More Posts Next page »
 
Page view tracker