Monday, December 31, 2007

.NET Framework Developer's Guide: Asynchronous File I/O

.NET Framework Developer's Guide: Asynchronous File I/O




Imports System
Imports System.IO
Imports System.Threading
Imports System.Runtime.InteropServices
Imports System.Runtime.Remoting.Messaging
Imports System.Security.Permissions



Module BulkImageProcAsync
Dim ImageBaseName As String = "tmpImage-"
Dim numImages As Integer = 200
Dim numPixels As Integer = 512 * 512

' ProcessImage has a simple O(N) loop, and you can vary the number
' of times you repeat that loop to make the application more CPU-
' bound or more IO-bound.
Dim processImageRepeats As Integer = 20

' Threads must decrement NumImagesToFinish, and protect
' their access to it through a mutex.
Dim NumImagesToFinish As Integer = numImages
Dim NumImagesMutex(-1) As [Object]
' WaitObject is signalled when all image processing is done.
Dim WaitObject(-1) As [Object]

Structure ImageStateObject
Public pixels() As Byte
Public imageNum As Integer
Public fs As FileStream
End Structure


_
Sub MakeImageFiles()
Dim sides As Integer = Fix(Math.Sqrt(numPixels))
Console.Write("Making {0} {1}x{1} images... ", numImages, sides)
Dim pixels(numPixels) As Byte
Dim i As Integer
For i = 0 To numPixels
pixels(i) = 255
Next i
Dim fs As FileStream
For i = 0 To numImages
fs = New FileStream(ImageBaseName + i.ToString() + ".tmp", FileMode.Create, FileAccess.Write, FileShare.None, 8192, False)
fs.Write(pixels, 0, pixels.Length)
FlushFileBuffers(fs.SafeFileHandle.DangerousGetHandle())
fs.Close()
Next i
fs = Nothing
Console.WriteLine("Done.")

End Sub


Sub ReadInImageCallback(ByVal asyncResult As IAsyncResult)
Dim state As ImageStateObject = CType(asyncResult.AsyncState, ImageStateObject)
Dim stream As Stream = state.fs
Dim bytesRead As Integer = stream.EndRead(asyncResult)
If bytesRead <> numPixels Then
Throw New Exception(String.Format("In ReadInImageCallback, got the wrong number of " + "bytes from the image: {0}.", bytesRead))
End If
ProcessImage(state.pixels, state.imageNum)
stream.Close()

' Now write out the image.
' Using asynchronous I/O here appears not to be best practice.
' It ends up swamping the threadpool, because the threadpool
' threads are blocked on I/O requests that were just queued to
' the threadpool.
Dim fs As New FileStream(ImageBaseName + state.imageNum.ToString() + ".done", FileMode.Create, FileAccess.Write, FileShare.None, 4096, False)
fs.Write(state.pixels, 0, numPixels)
fs.Close()

' This application model uses too much memory.
' Releasing memory as soon as possible is a good idea,
' especially global state.
state.pixels = Nothing
fs = Nothing
' Record that an image is finished now.
SyncLock NumImagesMutex
NumImagesToFinish -= 1
If NumImagesToFinish = 0 Then
Monitor.Enter(WaitObject)
Monitor.Pulse(WaitObject)
Monitor.Exit(WaitObject)
End If
End SyncLock

End Sub


Sub ProcessImage(ByVal pixels() As Byte, ByVal imageNum As Integer)
Console.WriteLine("ProcessImage {0}", imageNum)
Dim y As Integer
' Perform some CPU-intensive operation on the image.
Dim x As Integer
For x = 0 To processImageRepeats
For y = 0 To numPixels
pixels(y) = 1
Next y
Next x
Console.WriteLine("ProcessImage {0} done.", imageNum)

End Sub


Sub ProcessImagesInBulk()
Console.WriteLine("Processing images... ")
Dim t0 As Long = Environment.TickCount
NumImagesToFinish = numImages
Dim readImageCallback As New AsyncCallback(AddressOf ReadInImageCallback)
Dim i As Integer
For i = 0 To numImages
Dim state As New ImageStateObject()
state.pixels = New Byte(numPixels) {}
state.imageNum = i
' Very large items are read only once, so you can make the
' buffer on the FileStream very small to save memory.
Dim fs As New FileStream(ImageBaseName + i.ToString() + ".tmp", FileMode.Open, FileAccess.Read, FileShare.Read, 1, True)
state.fs = fs
fs.BeginRead(state.pixels, 0, numPixels, readImageCallback, state)
Next i

' Determine whether all images are done being processed.
' If not, block until all are finished.
Dim mustBlock As Boolean = False
SyncLock NumImagesMutex
If NumImagesToFinish > 0 Then
mustBlock = True
End If
End SyncLock
If mustBlock Then
Console.WriteLine("All worker threads are queued. " + " Blocking until they complete. numLeft: {0}", NumImagesToFinish)
Monitor.Enter(WaitObject)
Monitor.Wait(WaitObject)
Monitor.Exit(WaitObject)
End If
Dim t1 As Long = Environment.TickCount
Console.WriteLine("Total time processing images: {0}ms", t1 - t0)

End Sub


Sub Cleanup()
Dim i As Integer
For i = 0 To numImages
File.Delete(ImageBaseName + i.ToString + ".tmp")
File.Delete(ImageBaseName + i.ToString + ".done")
Next i

End Sub


Sub TryToClearDiskCache()
' Try to force all pending writes to disk, and clear the
' disk cache of any data.
Dim bytes(100 * (1 << i =" 0" bytes =" Nothing" length =" 1" processimagerepeats =" Int32.Parse(args(0))"> _
Sub FlushFileBuffers(ByVal handle As IntPtr)
End Sub
End Module



Here is a synchronous example of the same idea.
Imports System
Imports System.IO
Imports System.Threading
Imports System.Runtime.InteropServices
Imports System.Runtime.Remoting.Messaging
Imports System.Security.Permissions



Module BulkImageProcSync
Dim ImageBaseName As String = "tmpImage-"
Dim numImages As Integer = 200
Dim numPixels As Integer = 512 * 512

' ProcessImage has a simple O(N) loop, and you can vary the number
' of times you repeat that loop to make the application more CPU-
' bound or more IO-bound.
Dim processImageRepeats As Integer = 20

_
Sub MakeImageFiles()
Dim sides As Integer = Fix(Math.Sqrt(numPixels))
Console.Write("Making {0} {1}x{1} images... ", numImages, sides)
Dim pixels(numPixels) As Byte
Dim i As Integer
For i = 0 To numPixels
pixels(i) = 255
Next i
Dim fs As FileStream
For i = 0 To numImages
fs = New FileStream(ImageBaseName + i.ToString + ".tmp", FileMode.Create, FileAccess.Write, FileShare.None, 8192, False)
fs.Write(pixels, 0, pixels.Length)
FlushFileBuffers(fs.SafeFileHandle.DangerousGetHandle())
fs.Close()
Next i
fs = Nothing
Console.WriteLine("Done.")

End Sub


Sub ProcessImage(ByVal pixels() As Byte, ByVal imageNum As Integer)
Console.WriteLine("ProcessImage {0}", imageNum)
Dim y As Integer
' Perform some CPU-intensive operation on the image.
Dim x As Integer
For x = 0 To processImageRepeats
For y = 0 To numPixels
pixels(y) = 1
Next y
Next x
Console.WriteLine("ProcessImage {0} done.", imageNum)

End Sub


Sub ProcessImagesInBulk()
Console.WriteLine("Processing images... ")
Dim t0 As Long = Environment.TickCount
Dim pixels(numPixels) As Byte
Dim input As FileStream
Dim output As FileStream
Dim i As Integer
For i = 0 To numImages
input = New FileStream(ImageBaseName + i.ToString + ".tmp", FileMode.Open, FileAccess.Read, FileShare.Read, 4196, False)
input.Read(pixels, 0, numPixels)
input.Close()
ProcessImage(pixels, i)
output = New FileStream(ImageBaseName + i.ToString + ".done", FileMode.Create, FileAccess.Write, FileShare.None, 4196, False)
output.Write(pixels, 0, numPixels)
output.Close()
Next i
input = Nothing
output = Nothing
Dim t1 As Long = Environment.TickCount
Console.WriteLine("Total time processing images: {0}ms", t1 - t0)

End Sub


Sub Cleanup()
Dim i As Integer
For i = 0 To numImages
File.Delete(ImageBaseName + i.ToString + ".tmp")
File.Delete(ImageBaseName + i.ToString + ".done")
Next i

End Sub


Sub TryToClearDiskCache()
Dim bytes(100 * (1 << i =" 0" bytes =" Nothing" length =" 1" processimagerepeats =" Int32.Parse(args(0))"> _
Sub FlushFileBuffers(ByVal handle As IntPtr)
End Sub
End Module

See Also:
Reference: Stream Stream.Read Stream.Write Stream.BeginRead Stream.BeginWrite Stream.EndRead Stream.EndWrite IAsyncResult Mutex
Other Resources: File and Stream I/O