Blog Archives

Capturing Errors in MSBuild tasks

This may have been covered, but only stumbling upon this myself, I felt I would share it as it might help someone else out there.  I will give the background first as it might provide valuable context.

I have been creating and automating our .NET build processes using MSBuild.  MSBuild provides a great level of flexibility with how you can perform your builds but, it does lack in some areas.  But the areas that it lacks in, thankfully the gap is filled by creating custom tasks and this has been the basis for some projects on CodePlex and mainly the MSBuild Extension Pack.  This I believe fills almost all of the gaps that are missing from the normal tasks of MSBuild. 

So, armed with all of these, I set out to create the the build.

  1. The build was performed as normal.
  2. I performed the compile of the deployment projects (which aren’t supported out of the box for MSBuild) by using the Exec command for devenv /build.
  3. Got the assembly file version using the  MSBuild.ExtensionPack.Framework.Assembly and the GetInfo task.
  4. Created folders for the setup kit to be deployed to.
  5. Copied the MSI and setup.exe files to the deployment folders.
  6. Send email when all is well, using the MSBuild.ExtensionPack.Communication.Email and the Send task.
  7. Send email when process failed.

The emailing part was the main cause for concern for me.  If everything worked, I got an email, this was what I had intended.  If the compile fails, I also get an email, which is what I had intended.  But, if steps 2 to 5 fail for any reason, I receive no email. 

The reason for this is the email on the compile failed is in the target of BeforeOnBuildBreak which is where it should be as this target is only called when a build is broken.

So I need to capture the error.  I did this by using the OnError task.  Placed at the end of the task you are checking,

<OnError ExecuteTargets=”PartialSuccess”/>

So, in the target if an error happens that is outside the compile process, will execute the target ParialSuccess.  And with an aptly named <Target Name=”PartialSuccess”> and within is the MSBuild.ExtensionPack.Communication.Email.

Below is an example of what I mean.

Build Definition with OnError

So the AfterDropBuild is performed once the binaries have been put into the drop location.  If any of these processes fail in this AfterDropBuild section, it will then transfer execution to the target to PartialSuccess. The emails I have provided here show also how one can reference the build details such as log files.

P.S  I am sorry to those I tried to post the code on there directly but it seems that it was escaping all of the characters < > and others.  So I created an XML file.  This isn’t a complete project file, it is used just to show an example of sending emails when the non-compile process fails.

Advertisements

ShowDialog – Auto close or stopping the close

I have come across this issue recently and decided to post something on it.  I also found a few posts on the internet on it, but to me, they never really explained it properly.  Not to mention that the solution is pretty simple, but I find often the more complex the problem the easier it can be to find the solution, that the simpler ones could take longer not because they are hard but because it is so simple, not to mention it is staring you in the face and you can’t seem to see it.  So this is why I am posting it.

I have a dialog;

image

There is it.  I used to have the property DialogResult automatically set to DialogResult.Cancel and DialogResult.OK for the buttons.  When using the ShowDialog method, clicking either of these buttons will close the dialog automatically, and return the result based on the button pressed.

The issue I came across was performing field validation on the Value field when clicking OK.  But the message would show but the dialog would still close.  I fixed this by,

  1. Removing the property values of DialogResult from the buttons.  Setting them to None.
  2. Putting in the click events into each button the return of the dialog result.
Private Sub OKButton_Click(ByVal sender as System.Object, ByVal e as System.EventArgs) _
Handles OKButton.Click
	If ValidateValue() Then
		Me.DialogResult = DialogResult.OK
	End If
End Sub

Private Sub CancelButton_Click(ByVal sender as System.Object, ByVal e as System.EventArgs) _
Handles CancelButton.Click
	Me.DialogResult = DialogResult.Cancel
End Sub

Then the code in the main form, would handle the result coming back.  Note that in the OKButton that is ValidateValue is False then the dialog will stay there.

That is how I solved it, I hope this may come of use to anyone who was using ShowDialog for a form and has problems with it automatically closing when the buttons have the DialogResult property set to a value at design time.

DoEvents Strikes Back – Part 1

I have needed to butt heads with a colleague over their excessive use of DoEvents throughout their code.  I have seen many times DoEvents used as a crutch for poor performing code.  The form isn’t refreshing and the user has no idea what is happening, DoEvents.  Unable to have a cancel button working, DoEvents.  A loop taking too long to process, DoEvents.

First things first.  DoEvents was used as a mechanism to process windows message events on the queue for the current application.  Its main use, and it should have been always for this is the implementation of long running processes and processing a cancel mechanism for the process.  But I have seen it in non-looping processes, like in the case for me butting heads, and certainly other places, where it looks like they are trying to implement some visual multi-threading (by that I mean the user might think the application is multi-threaded when it isn’t.

Now DoEvents in the .NET Framework is implemented but is implemented differently from the one in VB6.  Though my main issue is it’s use in VB6 when it isn’t fully understood why it is there.  This annoyed me so much so that I eventually wrote a version of my own that is the same as the one implemented in .NET.

Private Sub DoAction()
    Dim i As Long
    Me.StatusBar.Value = 0
    For i = 1 to 10000000 ‘(yes 10 million)
        PerformActions i
        If i Mod 100000 = 0 then
            Me.StatusBar.Value = Me.StatusBar.Value + 1
        End If
    Next i
End Sub

Take a look at this code.  The application looks through, it increments a status bar on the form.  If the PerformActions is firing events and handling them, the refresh and update events on the form will not be executed until all events are processes.

Private Sub DoAction()
    Dim i As Long
    Me.StatusBar.Value = 0
    For i = 1 to 10000000 ‘(yes 10 million)
        PerformActions i
        If i Mod 100000 = 0 then
            Me.StatusBar.Value = Me.StatusBar.Value + 1
        End If
        DoEvents()
    Next i
End Sub

The above code shows the common place for DoEvents.  In the above case, DoEvents is executed 10 million times. this is possibly a little extreme.  The best place for DoEvents is in the PerformActions method, because it can be then selectively executed IF other events need to be processes.

Some might question what is wrong with the position of DoEvents in the above code.  It is the sleeping side effect that happens.  DoEvents is called, it then starts processing the events on the queue.  Once it returns it calls the Sleep API function, to end out the time slice give to it by the operating system.  The main problem is, the time slice is given is dependant on what needs to be processed.  If there is nothing on the queue, there is still a moment of sleeping, milliseconds, sure, but multiply milliseconds by 10 million and it might be significant.

And to give you an example.  The time to execute code the first example above, the one without DoEvents, it takes 0.389 seconds.  Not too bad for running a loop 10 million times

Then the second examples takes 3.56 seconds.  10 times longer, why, all because of DoEvents.  And this would be greater if there is actually events to be processed.  Don’t believe me.  Take both pieces of code and include a

Dim start As Single
Dim finish As Single

start = Timer
For i = 1 To 10000000
...
Next i
finish = Timer
MshBox finish - start

Include the above code in and it will show you the difference between both methods.  In these cases, PerformActions is merely adding the 1 to the number that is passed in.

So if I am saying how bad DoEvents is, what can be done.  If for whatever reason, this is the dialog.

image

So I have this, I click Click Me, something happens and I can click Stop to stop it processing.  Problem, which I have mentioned is, since the Click Me event is running, this event will continue to process until complete and no other event on the form will process while this happens.  So I can’t click Stop to stop it even if I wanted to.

But with this, why.  Get rid of the button, remove the check to see if I need to stop processing, since it takes .3 of a second, it isn’t long enough to really add all that additional code in there to enable the process to be stopped.

But I am not here to explain when is a good time to use it.  In reality I did show the reason for it, but in reality, I did also state is it needed.  I would rather remove it completely from the code and save myself 2.7 seconds than provide an ability for the user to cancel a very quick process.

So, if using DoEvents is not good what is there as an option?  Roll your own.  In the early days of VB it was all about the languages not having enough features and therefore you needed a little from the Win32API to improve controls or applications to give them features that they were used to, but didn’t exist in older versions of controls.

But I have a better solution.  How much better, a lot.

'API Point Structure
Private Type PointAPI
   x As Long
   y As Long
End Type

'Windows Message Structure
Private Type winMsg
    hwnd As Long       
    message As Long
    wParam As Long
    lParam As Long
    time As Long
    pt As PointAPI
End Type

'API declaration
Private Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" (lpMsg As winMsg, _
	ByVal hwnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, _
	ByVal wRemoveMsg As Long) As Long
Private Declare Function TranslateMessage Lib "user32" (lpMsg As winMsg) As Long
Private Declare Function DispatchMessage Lib "user32" Alias "DispatchMessageA" (lpMsg As winMsg) _
	As Long

Private Const PM_NOREMOVE = &H0
Private Const PM_REMOVE = &H1
Private Const PM_NOYIELD = &H2

'Only usable on Windows 2000 and greater
Private Const PM_QS_INPUT = &H70000
Private Const PM_QS_PAINT = &H200000
Private Const PM_QS_POSTMESSAGE = &H980000
Private Const PM_QS_SENDMESSAGE = &H400000


Public Sub ProcessWindowsMessages(Optional ByVal hWnd As Long = 0)
   Dim currentMessage As winMsg
    
	'Look at the messages on the queue.  We are processing them all for the current window, 
	'or all on this thread.
   Do While Not PeekMessage(currentMessage , hWnd , 0, 0, PM_REMOVE) = 0

      'Convert any virtual keystrokes into real ones, if there are any, and process them
      TranslateMessage currentMessage 
		
		'Process the message
      DispatchMessage currentMessage 
    Loop
End Sub

Well that is a lot of code to replace a small pre-defined function DoEvents.  Yes, but if you take that code, put it in a module, then replace the DoEvents with ProcessWindowsMessages you will find a very good increase in time. 

Here is my complete code listing for the form.

Private booStop As Boolean

Private Sub cmdButton_Click()
    Dim i As Long
    Dim start As Single
    Dim finish As Single
    
    Me.StatusBar.Value = 0
    start = Timer
    For i = 1 To 10000000
        'Check to see if the stop button was pressed.
        If booStop = True Then
            Exit For
        End If
        
        'perform the actions needed
        PerformActions i
        
        'Increment the status bar if it is time
        If i Mod 100000 = 0 Then
            Me.StatusBar.Value = Me.StatusBar.Value + 1
        End If
        
        'Do Events
        ProcessWindowsMessages
    Next i
    finish = Timer
    MsgBox finish - start
End Sub

Private Sub PerformActions(number As Long)
    number = number + 1
End Sub

Private Sub cmdStop_Click()
    booStop = True
End Sub

Private Sub Form_Load()
    booStop = False
End Sub

So I have included the timers in this full listing, which is OK, but I have the ProcessWindowsMessages procedure all.

image  imageimage

So, try it for yourself.  In both ProcessWindowsMessages and DoEvents cases, the stop button works. (Yes I included some additional information on the dialog box)

image image

Stop was pressed, but you need to be quick considering how long it takes to process the 10 million iterations.

image image

Using DoEvents, means there is more time for me to click the button to stop processing.

image

And with this, I can’t even click the button, not that I would have time to in honesty to even click a stop button.

I have presented the facts, VB6 and DoEvents has been a blight on a lot of my coding career and trying to see and teach people there are better ways of doing things.  In Part 2 I will explore the other methods in which you can process long running requests without the need to implement DoEvents or even ProcessWindowsMessages but still maintain a responsiveness to the calling application.