Dealing with Long Running Activities

When you run a long-running activity the workflow become “unresponsive”. Even if the UI might not be affected, it can cause problem in e.g. TFS build: basically the build cannot be interrupted during this long-running activity. We will put focus on that now.

As you may already know, our key will be calling these activities asynchronously, in a separate thread. You can read further on this topic on many sites. E.g.:

Using AsyncCodeActivity – The APM pattern

Yes, this pattern is pretty handy with the AsyncCodeActivity class. You can just simply create your derived class like this:

public class SimpleAsyncCodeActivity AsyncCodeActivity
    private const int BufferSize = 255;

    private byte[] _buffer;
    private FileStream _stream;

    public InArgument<string>  InputFile { getset; }
    public OutArgument<string> Result    { getset; }

    protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context,
                                                                    AsyncCallback callback, object state)
        var inputFileName = context.GetValue(InputFile);
        _buffer = new byte[BufferSize];
        _stream = new FileStream(inputFileName, FileMode.Open, FileAccess.Read);

        var cnt = Math.Max((int)_stream.Length, BufferSize);

        return _stream.BeginRead(_buffer, 0, cnt, callback, state);

    protected override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)

        context.SetValue(Result, String.Join("", _buffer));

Event-based Asynchronous Pattern

Sorry I did not spend effort on this topic. That might be not the most straightforward. Maybe I will go around this also, however the below described Task-based  way looks more preferred way – you may consider using this instead.

Task-based Asynchronous Pattern

Looks the most interesting. 🙂 I implemented it by creating an abstract activity class, based on AsyncCodeActivity.

public abstract class TaskBasedActivity<T> : AsyncCodeActivity<T>
    private readonly CancellationTokenSource _cancellationTokenSource;

    public OutArgument<Exception> Error { getset; }

    protected TaskBasedActivity()
        _cancellationTokenSource = new CancellationTokenSource();

    protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback,
                                                    object state)

        var task = new Task<T>(o => Execute(_cancellationTokenSource.Token, state),
                                state, _cancellationTokenSource.Token);

        task.ContinueWith(s => callback(s));

        return task;

    protected sealed override T EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
        var task = result as Task<T>;
        if (task == nullthrow new InvalidOperationException(String.Format("Activity returned with invalid result ({0}).", result.GetType().Name));

        context.SetValue(Error, task.Exception);

        if (task.IsCompleted && !task.IsFaulted && !task.IsCanceled)
            return task.Result;

        return NullResultValue;

    protected override void Cancel(AsyncCodeActivityContext context)

    protected abstract T Execute(CancellationToken cancellationToken, object state);
    protected abstract void LoadArguments(AsyncCodeActivityContext context);

    protected abstract T NullResultValue { get; }

The usage then looks like this:

public class SimpleTaskActivity TaskBasedActivity<int>
    private int _data;

    public InArgument<int> Data { getset; }

    protected override int NullResultValue
        get return -1; }

    protected override void LoadArguments(AsyncCodeActivityContext context)
        _data = context.GetValue(Data);

    protected override int Execute(CancellationToken cancellationToken, object state)
        // ... do what you want ..

        // handle cancellation request like this:        

        // .. or like this:
        if (cancellationToken.IsCancellationRequested) return NullResultValue;

        int result = 1;
        return result;

That is not bad I think. However few things might need some explanation:

  • Activity parameters could be loaded only  synchronously, as after BeginExecute() the context object is being destroyed. In order to do this, they are loaded LoadArguments() “in advance”, and stored in local fields.
  • Calling the ContinueWith() method of the task ensures, that the workflow completition is realized, and EndExecute() is called accordingly.
  • NullResultValue is involved, which is returned by the EndExecute() method, when failure/cancellation happens. (This way could be nicer, any suggestion is welcomed.)

Tagged with: , , , , ,
Posted in .Net, TFS Build, Workflow Foundation

Some words about Bild Reasons…

Build reason seems very straightforward to understand.. However it can give some surprises – at least it gave to me. So here comes some explanation how doe it look like on a bit deeper level.

Build Reason values

Build Reason is stored in an Enum with FlagsAttribute – so many values can be selected at he same type. There are some combination already set up as values.

Combinations /  CheckInShelveset /  ValidateShelveset/  UserCreated /  ScheduleForced /  Schedule /  BatchedCI /  IndividualCI /  Manual
128 64 32 16 8 4 2 1
None 0                
Triggered 191 X   X X X X X X
All 255 X X X X X X X X

Ok.. but what values shall I get when I start a build?

Action BuildReason value
Manual start, Latest sources  Manual
Manual start, Latest sources with shelveset  ValidateShelveset / CheckInShelveset
Continuous Integration  IndividualCI
Gated Check-in  CheckInShelveset
Rolling builds  BatchedCI
Scheduled  Schedule / ScheduleForced

How to use Build Reason in the build workflow

  • You can find the build reason in the BuildDetail variable of the default workflow.
  • If you have something special, you can get this object with the GetBuildDetail activity, which returns with an IBuildDetail  instance, which has it in its Reason property.
  • You can simply use the InvokeForReason activity if you want to execute some activities for some specific build reasons only.

Effects of the Build Reason

I have not tried all, just collecting the usages and side effects of it…

– In the default build process, the Drop Folder is not created, when the BuildReason is ValidateShelveset or None, even if droplocation is specified and Copy Outputs to Drop Folder is true. In other words, if you just build a shelveset, then it won’t have any drop folder, even if you are trying to write log file into it. (Obviously you can align your wokflow to solve this…)

Tagged with: , , , , ,
Posted in TFS, TFS 2010, TFS Build

TFS Custom Build Activities and Friends

Well, it should not be that strange. It is pretty common, that you need custom activity to perform your build. However sometimes you run into strange situations, where you might not find any help on the net… So I tryed to collect my experiences about this here.

(There are also many articles about this topic… I don’t want to repeat them – you can find links for some of them at the bottom.)

Project structure

Activities and Controls

Usually it is a good practice to keep the things together, and put your classes (activities, controls, argument classes) into a single assembly. Obviously in some cases it is not possible, or not preferrable – but in many cases makes your (and the config manager’s ) life easier. It is also elminates the issue of not loading an assembly when it is necessary.

However it is also a good practice to separate classes based on their function and level – so you can do it with namespaces. I’m using the below namespaces currently in order to separate my classes:

  • MyProject
  • MyProject.Controls
  • MyProject.Activities
  • MyProject.ViewModels

An exception is the assembly with the unit tests – obviously it is separated into another DLL, as it won’t get deployed to the productive TFS server.

Adding the Build Template (XAML)

Create a separate project (simple windows library) for the XAML files. (If it is in the same project as your activities, then VS may forgot to include the assembly explicitly when you import the namespace to the xaml. However don’t forget to add the required TFS and project references to the workflow project.

You can set the Build Action property of the xaml file to XamlAppDef – then VS will try to interpret it during the build, and may drive your attention to some error during the local build.

How the activity should look like

I will write details about the build activity types and their usages later on.

Testing and Debugging

Yes. The build runs on the Build Controller/Build Agent and not on the developer’s PC. To set up server environment on the dev PC might be too complex, sometimes maybe impossible, and testing on the server directly is not the most convenient way – but don’t worry, there are other possibilities also.

Unit Tests

Unit Tests are supported in most VS editions (if not in all). It runs locally, and can be debugged pretty easy. This can be automated also, so you can reuse your unit test to ensure that your code is always working. So let’s call your custom activity from a unit test, and mock the surrounding environment (or create a test environment). You can use the WorkflowInvoker for this.

        using Microsoft.VisualStudio.TestTools.UnitTesting;
        // ...

        public void MyActivitytest()
            var testObject = new object();
            var activityInputArguments = new Dictionary<stringobject>
                                    {"Argument2", testObject }
            var activityOutputArguments = WorkflowInvoker.Invoke(new MyCustomActivity(), activityInputArguments);

            var res = activityOutputArguments["Result"as MyResultClass;

            Assert.IsNotNull(res, "Activity returned with null result.");

Remote Debugging

Run a remote debugger (msvsmon.exe) on the Build Controller/Build Agent, and attach your Visual Studio to the remote TFS service process: TFSBuildServiceHost.exe. Then you can debug your code directly. Howevere unfortunatelly it does not always work, and may not stop at your breakpoint. (Any further suggestion, experience is welcomed.)

Old Times Are Back – Debug messages

When there is no other way you can use the old-style – add debug and trace output to your workflow/activity.

using Microsoft.TeamFoundation.Build.Workflow.Activities;

public class MyCustomActivity : CodeActivity
    protected override void Execute(CodeActivityContext context)
        // ...
        context.TrackBuildMessage("My build message");
        // ...


Signing Custom Assemblies

When you sign your custom assembly (and check in to the custom assemblies folder…), you might receive the below error message:

TF215097: An error occurred while initializing a build for build definition \MyProject\MyBuild: Cannot create unknown type ‘{clr-namespace:MyProject.Activities;assembly=MyProject}MyTestActivity’.

Well. fancy message, and there is no other trace usually to pick up the root cause. If you google the error code (TF215097), you will find plenty of pages, where several scenarios are described with the same error code. A reason behind this in this case, that the assembly is not linked properly from your build template xaml. If you have a strong-named assembly, then you need to specify the assembly properly, including version number and the public key token.

The proper syntax in the xaml file is below:

<Activity  ...
  xmlns:mpa="clr-namespace:MyProject.Activities;assembly=MyProject, Version="
  ... >

After doing this, the build should work.

How custom DLLs are loaded

Hehh.. Maybe not as you’d expect… 🙂 Assemblies are seached in the below order:

  • Specified CustomAssemby Path (on Build controller)
  • Visual Studio’s PrivateAssemblies folder
  • Global Assembly Cache

When you run the build, TSF loads all the assemblies from the CustomAssemblies folder, that contains activity in the specific HostEnvironment. Other assemblies from the CustomAssemblies folder might be not loaded at all!

Assemblies from PrivateAssemblies folder and GAC can be loaded on-demand.

Best practice: custom build assemblies should be kept in CustomAssemblies folder, and not in e.g. GAC. Otherwise the maintenance of these assemblies will be more complex (e.g. you have to deploy your DLLs to each build controllers/agents separately, although when it is kept in CustomAssemblies folder, they will be updated automatically for all build servers).


Sometimes you may see, that locally everything is fine, however the build server does not work as expected. In this case this checklist can help to find, what has been dropped out of the “deployment workflow”:

  • Always ensure that your workflow (xaml) and custom DLLs are checked in
  • Ensure that the custom assembly folder has been set up properly on the build controller
  • Ensure that assemblies are referenced properly in your xaml (with version and public key token for signed assemblies)
  • Ensure that no older copy exists in the GAC and/or in VS PrivateAssemblies folder

Also you may experience, that sometimes an assembly is just cannot be loaded from the CustomAssemblies folder, although it is referenced properly from xaml. In such case defining a dummy activity in the assembly with attribute [BuildActivity(HostEnvironmentOption.All)] may solve the problem.


Here comes some pretty good articles about the topic.

Tagged with: , , , , , ,
Posted in TFS, TFS 2010, TFS Build