Export (0) Print
Expand All
Around the World with Visual Basic
Asynchronous Method Execution Using Delegates
Building a Progress Bar that Doesn't Progress
Calling All Operators
Create a Graphical Editor Using RichTextBox and GDI+
Creating A Breadcrumb Control
Creating a Five-Star Rating Control
Creating and Managing Secondary Threads
Data Binding Radio Buttons to a List
Deploying Assemblies
Designing With Custom Attributes
Digital Grandma
Doing Async the Easy Way
Extracting Data from .NET Assemblies
Implementing Callbacks with a Multicast Delegate
Naming and Building Assemblies in Visual Basic .NET
Programming Events of the Framework Class Libraries
Programming I/O with Streams in Visual Basic .NET
Reflection in Visual Basic .NET
Remembering User Information in Visual Basic .NET
Advanced Basics: Revisiting Operator Overloading
Scaling Up: The Very Busy Background Compiler
Synchronizing Multiple Windows Forms
Thread Synchronization
Updating the UI from a Secondary Thread
Using Inheritance in the .NET World
Using the ReaderWriterLock Class
Visual Basic: Simplify Common Tasks by Customizing the My Namespace
What's My IP Address?
Windows Forms Controls: Z-order and Copying Collections
Expand Minimize
27 out of 43 rated this helpful - Rate this topic

Using Background Threads with Visual Basic 6

Visual Studio .NET 2003
 

Scott Swigart
Swigart Consulting LLC.

March 2006

Applies to:
   Microsoft Visual Basic 6
   Microsoft Visual Basic 2005
   Microsoft Visual Studio 2005

Summary: Learn how to use the .NET Framework 2.0 BackgroundWorker component from Visual Basic 6 applications to perform long running operations on background threads. This article shows you how to inform the user of progress, how to allow the user to cancel the background task, and how to debug multi-threaded applications. (10 printed pages)

Click here to download the code sample for this article.


Contents

Overview
Getting Started with the Article Sample Code
Using the BackgroundWorker
Canceling Background Tasks
Implementing Progress Notifications
Debugging Considerations
Conclusion

Overview

Applications can stop responding during long running operations, resulting in dissatisfied users. The traditional Visual Basic 6 technique to keep the application responding during a long running operation is to call DoEvents repeatedly. This allows the application to respond to events, and prevents the impression that the application has locked up. To provide a smooth user experience, DoEvents must be called several times a second. Sometimes, this simply isn't possible. If you execute a long running SQL statement, or are blocked waiting on an external resource, you do not have the ability to call DoEvents throughout the operation, and so the application appears to hang.

An advanced programming technique for handling long running operations is to perform the operation on a background thread. However, Visual Basic 6 does not support developing multi-threaded applications, and does not provide any built-in mechanism for starting background threads. However, Visual Basic .NET does support multi-threaded development, and Visual Studio 2005 includes a BackgroundWorker component that makes it easy to put operations on a background thread. This lets your application remain responsive while the operation progresses.

Normally, the BackgroundWorker component would only be available to .NET applications. However, this article includes a COM wrapper for the BackgroundWorker component so that it can be used seamlessly from COM environments, such as Visual Basic 6.

Getting Started with the Article Sample Code

Included in this article is a sample application that performs work in a background thread. To start using the sample, download and extract the code, and navigate into the "VS 2005 NetFX20Wrapper" folder. Open the solution file (.sln) with Visual Studio 2005. If you do not have Visual Studio 2005, you can download the free Visual Basic Express from http://lab.msdn.microsoft.com/express/vbasic/default.aspx, and use it instead. In fact, Visual Basic Express is a great way to try out many of the features of Visual Studio 2005, with no cost.

Once you have the solution open, select the Build | Build Solution menu command to compile the Visual Basic .NET wrapper that makes the BackgroundWorker component available as a COM object. You can then navigate to the "VB6 Application" folder and open the Visual Basic 6 sample application. When you run the application, it will perform a long running operation in the background, displaying the progress as the operation executes:

Figure 1. Sample application performing a long running operation

The application also gives you the ability to cancel the background operation. Next you will see how you can use the BackgroundWorker component from your own applications.

Using the BackgroundWorker

As the following walk-through will show, you can reference the BackgroundWorker component as though it were any simple COM object. BackgroundWorker exposes functions that let you start and stop the background operation. BackgroundWorker also exposes events to let you know how the work is progressing, and when it has completed.

Walkthrough: Setting up background work

  1. Open Visual Basic 6.0.
  2. In the New Project dialog, select Standard EXE, and click Open.
  3. Select the Project | References menu command.
  4. Check NetFx20Wrapper, and click OK.

    This adds a reference to the wrapper for the BackgroundWorker component, allowing you to use BackgroundWorker from Visual Basic 6.

  5. Select the Project | Add Module menu command.
  6. In the Add Module dialog, click Open.
  7. In the Code Editor, insert the following code:
    Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
    Public backgroundWorker As NetFX20Wrapper.BackgroundWorkerWrapper
    
    Public Sub BackgroundWork(ByRef argument As Variant, _
        ByRef e As NetFX20Wrapper.RunWorkerCompletedEventArgsWrapper)
    On Error GoTo eh
        Sleep argument
        e.SetResult "Background work done"
        Exit Sub
        
    eh:
        e.Error.Number = Err.Number
        e.Error.Description = Err.Description
    End Sub
    
    

The BackgroundWorker component will be used to run this subroutine on a background thread. The subroutine simply sleeps for a specified number of milliseconds and returns, to simulate a long running operation. This subroutine is also passed a RunWorkerCompletedEventArgsWrapper argument. This argument lets the subroutine indicate whether it was successful or not, and returns a result. If the subroutine completes without error, it will use e.Result to pass a return value back to the main thread. If it fails, the error handler will set error information that is passed back to the main thread. It is critical that the background thread does not have any unhandled errors. If any errors go unhandled in the background thread, the application will crash. For this reason, you always must set up an On Error Goto in your background subroutine, and use e.Error to return error information.

Walkthrough: Starting the background work

  1. In the Project Explorer, double-click Form1.
  2. Add a button to the form and set its caption to Start.
  3. Double-click the button to create the event handler.
  4. Replace the code in the code editor with the following:
    Dim WithEvents background As NetFX20Wrapper.BackgroundWorkerWrapper
    
    Private Sub Form_Load()
        Set background = New NetFX20Wrapper.BackgroundWorkerWrapper
    End Sub
    
    Private Sub Command1_Click()
        set module1.backgroundWorker = background
        background.RunWorkerAsync AddressOf BackgroundWork, 5000
    End Sub
    
    

    This creates an instance of the BackgroundWorker component. When the button is clicked, BackgroundWorker is used to start the BackgroundWork subroutine (created in the previous walkthrough) on a background thread. Also notice that an argument of 5000 will be passed to the subroutine. This is used inside of the subroutine to determine the number of milliseconds to sleep.

  5. In the Object drop-down menu, select background. In the Procedure drop-down menu, select RunWorkerCompleted. This will create an event handler that fires when the background work has finished.

    Figure 2. Create the event handler here

  6. Code the RunWorkerCompleted event handler as follows:
    Private Sub background_RunWorkerCompleted(ByVal sender As Variant, _
        ByVal e As NetFX20Wrapper.RunWorkerCompletedEventArgsWrapper)
        
        If e.Error.Number = 0 Then
            MsgBox e.GetResult
        Else
            MsgBox e.Error.Description
        End If
    End Sub
    
    

    This will check to see if the background work completed without error. If so, the return value from the background work will be displayed. Otherwise, an error message will be displayed.

  7. Select the Tools | Options menu command.
  8. Select the Environment tab.
  9. In the When a program starts group box, select Save Changes.
  10. Click OK.

    It's very important to save changes every time the application runs. If errors happen in the background subroutine, it can crash the development environment. If you don't save changes, this can result in a loss of work.

  11. Press F5.
  12. Select a folder to save all project files.
  13. The application starts:

    Figure 3. Application with Start button to begin the background operation

  14. Click the Start button. This starts the background operation, which will run for 5 seconds.
  15. Drag the form around to prove that the application is not blocked while the background subroutine is running.
  16. After five seconds, a message box is displayed indicating that the background subroutine completed. The message box contains a string returned from the background subroutine.

    Figure 4. Background work completed message box

  17. Click OK, and close the application.

Canceling Background Tasks

With the BackgroundWorker, you can also request that the background subroutine abort. I say request, because your background subroutine must periodically poll a property to see if a cancellation has been requested, and abort if so. In the walkthrough shown previously, the background subroutine calls Sleep, and the background subroutine isn't able to do anything else until Sleep returns, five seconds later. For this reason, the background subroutine would not be able to abort if a cancel request was sent, because it would not be able to process that cancel request until it was already finished processing.

For a background thread to be able to respond to a cancel request, it must be doing its work in discrete chunks, with the ability to check for a cancellation request between chunks. If you're making a call to a long running stored procedure, or otherwise waiting on an external resource, then the BackgroundWorker component is still very useful, as it allows you to put that operation in the background and keep the application responsive, but you will not be able to give the user the option to abort the operation once it begins.

For the purposes of demonstration, the background work in the walkthrough can be broken into smaller chunks so that you can see how cancel functionality is implemented.

Walkthrough: Canceling background operations

  1. In the Project Explorer, double-click Module1.
  2. Modify the BackgroundWork subroutine as follows:
    Public Sub BackgroundWork(ByRef argument As Variant, _
        ByRef e As NetFX20Wrapper.RunWorkerCompletedEventArgsWrapper)
    On Error GoTo eh
        For i = 1 To argument / 500
            Sleep 500
            If backgroundWorker.CancellationPending Then
                e.SetResult "Cancelled after " & i * 500 & " ms"
                e.Cancelled = True
                Exit Sub
            End If
        Next
        e.SetResult "Background work done"
        Exit Sub
        
    eh:
        e.Error.Number = Err.Number
        e.Error.Description = Err.Description
    End Sub
    
    

    This code has been modified so that the work is broken into 500 millisecond chunks. After each 500 millisecond unit of work the code checks the CancellationPending property of the BackgroundWorker component. If CancellationPending is true, then the routine sets the Cancelled property to true. This will let the main thread determine if the background work ran to completion, or if it was successfully cancelled. The background subroutine also sets the result message, and then exits.

  3. In the Project Explorer, double-click Form1.
  4. Add another button to the form, and set its caption to Cancel.
  5. Double-click the button to create its Click event handler.
  6. Code the Click event handler as follows:
    Private Sub Command2_Click()
        background.CancelAsync
    End Sub
    
    
  7. In the Project Explorer, right-click Form1, and select View Code.
  8. Modify background_RunWorkerCompleted as follows:
        If e.Cancelled Then
            MsgBox "Cancelled: " & e.GetResult
        ElseIf e.Error.Number = 0 Then
            MsgBox e.GetResult
        Else
            MsgBox e.Error.Description
        End If
    
    

    This routine can now check the Cancelled property to see if the background subroutine ran to completion, or was cancelled prematurely.

  9. Press F5 to run the application.
  10. Click the Start button, and then click the Cancel button. You should see a message like the following:

    Figure 5. Message box indicating cancellation of the background task

  11. Click OK, and close the application.

Implementing Progress Notifications

The BackgroundWorker component also provides facilities so that the background subroutine can send progress events to the main thread. This is useful if you want to display a progress bar while the background subroutine runs.

Walkthrough: Adding progress support

  1. In the Project Explorer, double-click Module1.
  2. Add a call to BackgroundWorker.ReportProgress as shown below:
    Public Sub BackgroundWork(ByRef argument As Variant, _
        ByRef e As NetFX20Wrapper.RunWorkerCompletedEventArgsWrapper)
    On Error GoTo eh
        For i = 1 To argument / 500
            Sleep 500
            If backgroundWorker.CancellationPending Then
                e.SetResult "Cancelled after " & i * 500 & " ms"
                e.Cancelled = True
                Exit Sub
            End If
            backgroundWorker.ReportProgress _
                (CDbl(i) / (argument / 500)) * 100
        Next
        e.SetResult "Background work done"
        Exit Sub
        
    eh:
        e.Error.Number = Err.Number
        e.Error.Description = Err.Description
    End Sub
    
    

    This sends a value from 0 to 100 indicating what percentage of the work has been completed.

  3. In the Project Explorer, double-click Form1.
  4. Right-click the Toolbar, and select Components.
  5. Check Microsoft Windows Common Controls 6.0, and click OK.
  6. Add a ProgressBar control to the form.
  7. Press F7 to switch to code view.
  8. In the Object drop-down, select background. In the Procedure drop-down, select ProgressChanged.
  9. Code the ProgressChanged event handler as follows:
    Private Sub background_ProgressChanged(ByVal sender As Variant, _
        ByVal e As NetFX20Wrapper.ProgressChangedEventArgsWrapper)
    
        ProgressBar1 = e.ProgressPercentage
    
    End Sub
    
    

    When the background subroutine calls ReportProgress, it causes the ProgressChanged event to fire on the main thread. In this event, you can look at the ProgressPercentage property to determine how much of the work has been done.

  10. Press F5 to run the application.
  11. Click Start. You should see the progress bar move as the background subroutine does work.

    Figure 6. Progress bar display

  12. Close the application.

Debugging Considerations

The Visual Basic 6 development environment was not designed to support debugging multi-threaded applications. For this reason, you cannot set a breakpoint in your background subroutine. If you do, the development environment will crash. Also, if your background subroutine has an unhandled exception, the development environment will also crash.

To facilitate debugging, the wrapper for the BackgroundWorker exposes a RunWorkerSync function. This works exactly like RunWorkerAsync, except the background subroutine is run on the main thread, instead of a background thread. This means that while the subroutine is running, your application will appear to lock up. However, this does allow you to safely set breakpoints in your background routine, and perform debugging.

Walkthrough: Debugging the background routine

  1. In the Project Explorer, right-click Form1, and select View Code.
  2. Modify the Button1_Click event as follows:
    Private Sub Command1_Click()
        Set Module1.backgroundWorker = background
        background.RunWorkerSync AddressOf BackgroundWork, 5000
    End Sub 
    
    

    Changing the call from RunWorkerAsync to RunWorkerSync will cause the background subroutine to run synchronously (on the main thread), so that it can be debugged.

  3. In the Project Explorer, double-click Module1.
  4. Set a breakpoint on the following line of code inside the BackgroundWork subroutine:
    For i = 1 To argument / 500
    
    
  5. Press F5 to run the application.
  6. Click on Start.

    Execution stops on the breakpoint, and you can debug without error.

  7. Clear the breakpoint, and press F5. Execution continues, but the application is not responsive until the BackgroundWork subroutine completes.

Conclusion

In this article, you have seen how the BackgroundWorked component from version 2.0 of the .NET Framework has been wrapped so that it can be used seamlessly from a Visual Basic 6 (or other COM based) application. This lets you perform long running operations on a background thread so that the application remains responsive, and does not appear to lock up or hang. The BackgroundWorker provides capabilities for aborting the background operation, and the background operation can provide events to the main thread, notifying it of the percentage completed.

 

About the author

Scott Swigart www.swigartconsulting.net, spends his time consulting with companies on how to best use today's technology and prepare for tomorrows. Along this theme, Scott is a proud contributor to the Visual Basic Fusion site, as it offers information and tactics of real use for Visual Basic developers who want to build the most functionality with the least effort. Scott is also a Microsoft MVP, and co-author of numerous books and articles. If you have any question or comments about this article, feel free to send e-mail to scott@swigartconsulting.com.

Show:
© 2014 Microsoft. All rights reserved.