Visual Basic 6 Asynchronous File I/O Using the .NET Framework
Visual Basic 6
Visual Studio 2005
Summary: Learn how to pass information to a background thread from an existing Visual Basic 6 application and how the results of the background work can then return from the background thread to the Visual Basic 6 application. (7 printed pages)
Download the associated Asynch File IO.exe sample code.
There are many applications that need to monitor directories and process incoming files. Files could appear as data exported from a legacy system, as FTP or HTTP uploads, or a variety of other sources. I set out to build an application in Visual Basic 6 that would process incoming files. The Visual Basic 6 application needed to satisfy the following requirements:
- A directory monitored on a background thread: The application needed to remain responsive and unblocked while waiting for files to appear.
- File contents loaded on a background thread so that the application would not be blocked while large files were being loaded.
- Some processing of the file contents.
If you're working with Visual Basic 6, you know that its quite capable of reading files. However, doing this kind of work on a background thread is beyond its core capabilities. On the other hand, Visual Basic 2005 fully supports coding background operations and you can easily call Visual Basic 2005 code from Visual Basic 6 applications.
This article will show how a Visual Basic 2005 component was created to perform the background operations to achieve the above requirements, as well as how this component was easily used from a Visual Basic 6 application.
By putting the background work in a separate component, the core Visual Basic 6 application becomes simple and easy to follow. The entire application is shown in Listing 1.
Listing 1: Visual Basic 6 application
Option Explicit Dim WithEvents dirWatcher As DropDirMonitor.DirWatcher Private Sub Form_Load() Randomize txtDropDir.Text = App.Path & "\Drop" Set dirWatcher = New DropDirMonitor.dirWatcher End Sub Private Sub cmdStart_Click() dirWatcher.Init txtDropDir.Text End Sub Private Sub cmdGenDataFile_Click() Dim i As Integer Dim fileName As String fileName = txtDropDir.Text & "\" & Rand(1, 100000) & ".dat" Open fileName For Output As #1 Print #1, "ProductID,Quantity,Price" For i = 1 To Rand(5000, 30000) Print #1, Rand(1, 100) & "," & Rand(1, 20) & "," & Rand(1, 50) Next Close #1 End Sub Private Sub dirWatcher_ProcessingFile(ByVal fileName As String) fileName = Right(fileName, Len(fileName) - InStrRev(fileName, "\")) Text1.Text = Text1.Text & "Processing:" & fileName DoEvents End Sub Private Sub dirWatcher_FileContentsAvailable(ByVal contents As String) Dim lines() As String lines = Split(contents, vbCrLf) Dim total As Double Dim line As Variant For Each line In lines Dim items() As String items = Split(line, ",") If UBound(items) > -1 Then If IsNumeric(items(1)) Then total = total + (CDbl(items(1)) * CDbl(items(2))) End If End If Next Text1.Text = Text1.Text & ", Total = " & FormatCurrency(total) & vbCrLf End Sub Public Function Rand(ByVal Low As Long, ByVal High As Long) As Long Rand = Int((High - Low + 1) * Rnd) + Low End Function
The Visual Basic 6 application creates an instance of the DropDirMonitor.DirWatcher component. This component will fire events when a new file shows up in a specified "drop directory." The events will let the Visual Basic 6 application know the name of the file and will provide the loaded file contents.
The Form_Load event in Visual Basic 6 will create a new instance of the DirWatcher class. When the user clicks the Start button, the application will initialize the DirWatcher to watch a specific directory for new files. When files appear in that directory, the ProcessingFile event will fire. This informs the Visual Basic 6 application that a new file has been detected for processing. The DirWatcher will then load the file contents into memory and fire the FileContentsAvailable event. Because the DirWatcher works on a background thread, if loading a very large file takes too long, the Visual Basic 6 application does not stall.
When the FileContentsAvailable event fires, it passes the file data to the Visual Basic 6 application for processing. Figure 1 shows the sample Visual Basic 6 application as it processes new files.
Figure 1. Visual Basic 6 application processing files
In this sample application, the file that appears in the drop directory will contain thousands of lines of CSV data. When the Visual Basic 6 application is given the contents, it parses the data and calculates a total.
With Visual Basic 2005, it's easy to create a component that can be called from Visual Basic 6. Simply create a new project and select ClassLibrary as the project type. On the Project menu, select Add New Item and add a COM Class to the project. When you compile the class library, it will also be registered as a COM object. You can reference it from Visual Basic 6, as well as call the functions in your COM classes.
The code download for this article includes a Visual Basic 2005 solution called DropDirWatcher. This is the Visual Basic 2005 code that watches for new files to appear, loads the file contents into memory, and makes the file contents available to the Visual Basic 6 application. This project contains a single file called DirWatcher.vb, which is a COM Class. The beginning of this file is shown in Listing 2.
Listing 2: Start of the DirWatcher class
<ComClass(DirWatcher.ClassId, DirWatcher.InterfaceId, DirWatcher.EventsId)> _ Public Class DirWatcher Inherits Control #Region "COM GUIDs" ' These GUIDs provide the COM identity for this class ' and its COM interfaces. If you change them, existing ' clients will no longer be able to access the class. Public Const ClassId As String = "db5b9156-4aa7-4a5d-a32d -ceb6af2dc3e2" Public Const InterfaceId As String = "2c22921f-2357-4215-9759 -0ba865a65d7a" Public Const EventsId As String = "1c1e49dc-d641-41b3-9394 -b003de631c4d" #End Region Private WithEvents mFileSystemWatcher As New FileSystemWatcher Private mDirectoryToWatch As String Private filesToProcess As New List(Of String) Public Sub New() MyBase.New() Me.CreateHandle() End Sub Public Sub Init(ByVal directoryToWatch As String) mDirectoryToWatch = directoryToWatch mFileSystemWatcher.Path = mDirectoryToWatch mFileSystemWatcher.EnableRaisingEvents = True For Each file As String In Directory.GetFiles(directoryToWatch) filesToProcess.Add(file) Next ProcessNextFile() End Sub Private Sub mFileSystemWatcher_Created(ByVal sender As Object, _ ByVal e As System.IO.FileSystemEventArgs) _ Handles mFileSystemWatcher.Created If Me.InvokeRequired Then If e.ChangeType <> WatcherChangeTypes.Created Then Return Me.Invoke(New EventHandler(Of FileSystemEventArgs) _ (AddressOf mFileSystemWatcher_Created), sender, e) Else filesToProcess.Add(e.FullPath) ProcessNextFile() End If End Sub
The .NET Framework contains a class known as the FileSystemWatcher. This class will monitor a directory and fire events when any changes are detected. In the Init method, the FileSystemWatcher is configured with the directory to watch and enable events. Now, any time a file is added to that directory, the mFileSystemWatcher_Created event will fire.
The mFileSystemWatcher_Created event may not fire on the same thread as the main Visual Basic 6 application. For this reason, you see that the mFileSystemWatcher_Created event contains an odd looking "If" statement. The purpose of this statement is to detect if the event has fired on a background thread. By checking Me.InvokeRequired, the code is able to determine whether it is on the main thread or a background thread. If InvokeRequired is true, then the function calls back to itself using Me.Invoke. This will martial the arguments to the main thread where the new file is added to the list of files that need to be processed.
To load in the file contents asynchronously, a BackgroundWorker component is used. This makes it easy to work on a background thread. The ProcessNextFile routine, shown in Listing 3, starts the BackgroundWorker on the first file to process.
Listing 3: Visual Basic.NET Component Starts File Processing
Private Sub ProcessNextFile() If filesToProcess.Count = 0 Then Return If Me.Cancel = True Then Return If Not mBackgroundWorker.IsBusy Then Dim fileToProcess As String = filesToProcess(0) filesToProcess.RemoveAt(0) If File.Exists(fileToProcess) Then mBackgroundWorker.RunWorkerAsync(fileToProcess) End If End If End Sub
This routine starts the file processing by calling BackgroundWorker.RunWorkerAsync, passing in the path to the first file to process. This causes the DoWork event associated with BackgroundWorker to fire on a background thread. Listing 4 shows how the file is loaded in this event.
Listing 4: Background work to load the file
Private Sub mBackgroundWorker_DoWork(ByVal sender As Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles mBackgroundWorker.DoWork Using sr As New StreamReader(File.Open( _ e.Argument, FileMode.Open, _ FileAccess.Read, FileShare.None)) mBackgroundWorker.ReportProgress(0, e.Argument) e.Result = sr.ReadToEnd() End Using File.Delete(e.Argument) End Sub
The routine opens the file for reading and then reads the entire file contents into memory. If the file is large, the application remains responsive because the DoWork function is running on a background thread. When the file contents are read, they are placed in the Result property of the event argument. In this way, the BackgroundWorker does the work of marshaling the file contents back to the main thread. When DoWork finishes, the BackgroundWorker fires the RunWorkerCompleted event, passing it the file contents as shown in Listing 5.
Listing 5: File contents have been loaded, notify the Visual Basic 6 application
Private Sub mBackgroundWorker_RunWorkerCompleted( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _ Handles mBackgroundWorker.RunWorkerCompleted If e.Result IsNot Nothing Then RaiseEvent FileContentsAvailable(e.Result) End If ProcessNextFile() End Sub
This routine raises the FileContentsAvailable event. This event is handled by the Visual Basic 6 application. The result is that the Visual Basic 6 application can remain responsive and handle any user operations, and when a file appears in the drop directory, the Visual Basic 6 application simply receives an event containing the file contents.
Note The full solution code is available for download from Microsoft: http://download.microsoft.com/download/3/6/7/367DCF3B-A23E-41BA-BF6A-7E8BFF4FF476/Async File IO.EXE.
Download the solution and use Visual Studio 2005 (or Visual Basic Express) to compile the DirWatcher component. You can then run the Visual Basic 6 application, which uses this component.
While this article focuses on background file processing, it really provides a framework that allows you to perform practically any operation on a background thread. This article shows how information can be passed to that background thread from an existing Visual Basic 6 application and how the results of the background work can be returned from the background thread to the Visual Basic 6 application.