Three Simple Rules for Better Debugging with WorkflowApplication

Developers often say that there is a steep learning curve with Windows Workflow Foundation (WF4). I won’t deny that… instead, allow me to share three simple rules that will help you to get over that curve sooner and to make them easy to remember, they all begin with “U” which is appropriate because they do all begin with you.

As you can see in the previous example, this is a mind numbing dump of data. If you want something much more useful try Microsoft.Activities.Extensions.Tracking which includes tracking record extensions designed to make it easy for you to follow what is going on.

Now that is a trace that helps you to understand exactly what is going on, including things that you don't see in the simple record.ToString() trace above such as arguments, annotations, variables and more.

Tracking to the Trace Provider

When debugging, just add a TraceTrackingParticipant to get the tracking output written to Visual Studio Debug Output window. Or if you are using a logging library such as NLog or Enterprise Library.

1:using Microsoft.Activities.Extensions.Tracking;

2:

3:privatestaticvoid RunWorkflow()

4: {

5: var host = new WorkflowApplication(WorkflowDefinition);

6:

7:// Tip: Output tracking to System.Diagnostics.Trace

8: host.Extensions.Add(new TraceTrackingParticipant());

9:

10:// ...

11: }

12:

Tracking to a File

If you just want a text file with the output

1:privatestaticvoid RunWorkflow()

2: {

3: var host = new WorkflowApplication(WorkflowDefinition);

4:

5:// Tip: Capture tracking to a file for help debugging

6:using (var fileTracker = new FileTracker("tracking.txt"))

7: {

8: host.Extensions.Add(fileTracker);

9: host.Run();

10:// Wait for it to complete and then

11:

12:// FileTracker is Disposable

13: }

14: }

15:

Unit Tests

What do we need to test?

There is a protocol of bookmarks that must be followed for this workflow to function correctly. Our tests should verify that the protocol is followed from the workflow side and from the host side. Using the Given / When / Then pattern helps me to be clear about what I’m testing and helps me to focus the test on just one aspect of the behavior.

Use Timeouts

When everything goes right, you don’t need timeouts. So you create programs that will one day hang because on that particular day something didn’t go right. I’m as guilty as anyone when it comes to this. Recently I’ve been reviewing the code I’ve written for Microsoft.Activities.Extensions and Microsoft.Activities.UnitTesting and I came up with these rules.

Any class that has one thread waiting on an asynchronous operation from another thread you must use a timeout and handle timeouts gracefully.

Classes should have a Default Timeout that will be used if one is not supplied by the caller

Timeouts should be consistent across the library

Timeouts should self-adjust when the debugger is attached

To implement this pattern, in the sample I have a class named Global which contains these shared global properties.

23:/// Gets or sets the default timeout used when a debugger is attached

24:/// </summary>

25:/// <remarks>

26:/// Allows users of this library to set the default

27:/// </remarks>

28:internalstatic TimeSpan DefaultDebugTimeout

29: {

30: get

31: {

32:return defaultDebugTimeout;

33: }

34:

35: set

36: {

37: defaultDebugTimeout = value;

38: }

39: }

40:

41:/// <summary>

42:/// Gets or sets the default timeout

43:/// </summary>

44:/// <remarks>

45:/// Allows users of this library to set the default

46:/// </remarks>

47:internalstatic TimeSpan DefaultTimeout

48: {

49: get

50: {

51:return defaultTimeout;

52: }

53:

54: set

55: {

56: defaultTimeout = value;

57: }

58: }

59:

60:/// <summary>

61:/// Gets Timeout used for wait operations

62:/// </summary>

63:/// <remarks>

64:/// TODO: Notice how the Timeout adjust when the debugger is attached

65:/// </remarks>

66:internalstatic TimeSpan Timeout

67: {

68: get

69: {

70:return Debugger.IsAttached ? defaultDebugTimeout : defaultTimeout;

71: }

72: }

73:

74:#endregion

75: }

How many times have you decided to debug your program only to get TimeoutException because you were slowly stepping through the code? With this approach when I do an operation that needs a timeout, I get a consistent timeout that automatically adjusts when a debugger is attached.

// Use Microsoft.Activities.Extensions to run until idle with a bookmarkhost.RunEpisode(Bookmark1, Global.Timeout);

// Whenever you use WaitOne you MUST use a timeoutif (!idleEvent.WaitOne(Global.Timeout)){thrownew TimeoutException();}

Summary

Any time you create multi-threaded programs you have crossed over into advanced territory. Whenever you use WorkflowApplication you are writing a multi-threaded program and you cannot escape the need for these three simple rules.