Question How do I perform an asynchronous file save which for events that track progress and completion?

Joined
Sep 16, 2023
Messages
20
Programming Experience
10+
WHAT I HAVE:
Visual Basic 2019, .NET 4.6+, WinForms

MY ISSUE:
I'm creating a custom WinFroms control that's supposed to do several things, including the one I'm talking about here--which is why I hope that the solution to my question doesn't require a lot of complex, convoluted, confusing code here. I want to save a large amount of data to a file asynchronously with members like the following, using public methods SaveAsync and CancelAsync, and public events SaveProgressChanged and SaveComplete:

Methods and events for Asynchronous save:
Private Sub BackgroundTask()

'   logic to perform asynchronous save operation in background

End Sub


Public Event SaveProgressChanged(sender As Object, e As ProgressChangedEventArgs)



Public Event SaveCompleted(sender As Object, e As AsychCompletedEventArgs)



Public Sub SaveAsync(ByVal FileName As String)

'   logic to initiate asynchronous save

End Sub



Public Sub CancelAsync()

'   logical to cancel asnchronous save

End Sub

I want the background task to save the data in increments of a certain size, each time firing SaveProgressChanged to report progress thereafter, and to fire SaveComplete, with data on whether the proces finsihed, cancelled, or was aborted by error, at the end. I've been studying help-file examples on asynchronous operations and file I/O but I'm not sure how best to get all my specific desired results.

What's the best way to do it? My options seem to be using FileStream asynchrounously (no help-file examples apper to exist, though, for how to write multiple BeginWrite/EndWrite blocks in the same stream, one after the other) or using BackgroundWorker with FileStream (synchronously with multiple Write blocks, or asynchronously with multiple BeginWrite/EndWrite blocks [again, how is that done?], inside the DoWork event). Maybe there are other ways. What's the most reliable yet simple way to do this? I don't to write a ton of spagetti code, but I want to get it done.

Please answer ASAP, and keep it as simple as possible.
 
Last edited:
Saving data to a file should not be the job of a control. Controls are UI elements for user interaction. File I/O should be handled by complete separate classes. It sounds like you're going back to the bad old days when VB6 controls did everything under the sun. The .NET paradigm is to obey the Single Responsibility Principle and have each class focused on a single job. You should rethink your design.
 
Saving data to a file should not be the job of a control. Controls are UI elements for user interaction. File I/O should be handled by complete separate classes. It sounds like you're going back to the bad old days when VB6 controls did everything under the sun. The .NET paradigm is to obey the Single Responsibility Principle and have each class focused on a single job. You should rethink your design.

The standard VB.NET WinForms PictureBox control already allows synchronous and asynchronous file loading. My extended version would like to do the same thing with file saving. BTW, I need to know how to save info to a file--for whatever purpose--asynchronously whne there's more than 1 block to write. I believe that the right approach has to be using asynchronous FileStream methods, or placing either synchronous or asynchronous FileStream methods within the DoWork event of a BackgroundWorker class. Besides, the help files don't show examples of asynchronous file I/O with multiple BeginWrite / EndWrite incidents on a single stream--in case one has to write more than 1 block asynchronously to the file. I'm looking for advice in general regarding asynchronous file saving involving multiple data blocks and progress-tracking.

How about this?

File-saving code:
    '            handle asynchrounous image-saving

    Private Sub bwSaveImage_DoWork(sender As Object, _
        e As DoWorkEventArgs) Handles bwSaveImage.DoWork
    '   save in 16K increments and write sequentially directly to disk
    Const BufferSize As Integer = 16 * 1024, _
        SaveOptions As IO.FileOptions = _
            IO.FileOptions.SequentialScan Or IO.FileOptions.WriteThrough
    '   create buffer and get file name and memory stream
    e.Result = False 'assume failure at first
    Dim SaveBuffer(BufferSize) As Byte, _
        SaveInfo As SaveInfo = DirectCast(e.Argument, SaveInfo)
    With bwSaveImage
        '   copy memory stream to file stream
        Using ImageStream As IO.MemoryStream = SaveInfo.ImageStream, _
                FileStream As IO.FileStream = _
                    IO.File.Create(SaveInfo.FileName, BufferSize, SaveOptions)
            Dim NumberOfBytes As Long = FileStream.Length, BytesWritten As Long = 0
            Try
                '   do transfer 16K bytes at a time
                Do While BytesWritten < NumberOfBytes
                    If .CancellationPending Then
                        Exit Do 'operation being cancelled
                     Else
                        '   transfer a chunk to the file
                        Dim BytesRead As Integer = ImageStream.Read(SaveBuffer, 0, BufferSize)
                        FileStream.Write(SaveBuffer, 0, BytesRead)
                        BytesWritten += BytesRead
                        '   report progress
                        Dim PercentProgress As Integer = _
                            CType(BytesWritten * 100L \ NumberOfBytes, Integer)
                        .ReportProgress(PercentProgress)
                    End If
                Loop
                '   report whether or not we saved the full image
                e.Result = (BytesWritten = NumberOfBytes)
             Finally
                ImageStream.Close() : FileStream.Close()
            End Try
        End Using
    End With
    End Sub

    Private Sub bwSaveImage_ProgressChanged(sender As Object, _
        e As ProgressChangedEventArgs) Handles bwSaveImage.ProgressChanged
    '   fire SaveProgressChanged event
    Me.OnSaveProgressChanged(e)
    End Sub

    Private Sub bwSaveImage_RunWorkerCompleted(sender As Object, _
        e As RunWorkerCompletedEventArgs) Handles bwSaveImage.RunWorkerCompleted
    '   fire SaveCompleted event
    CurrentAsyncAction = PictureBoxExAsyncAction.None
    Me.OnSaveCompleted( _
        New AsyncCompletedEventArgs(e.Error, e.Cancelled, e.UserState))
    End Sub

BTW, SaveInfo.ImageStream is a memory stream that the image is first saved to.
 
Last edited:
Back
Top