비동기 파일 I/O
업데이트: 2007년 11월
동기 I/O는 I/O 연산이 완료될 때까지 메서드가 차단된 다음 메서드에서 해당 데이터를 반환한다는 것을 의미합니다. 비동기 I/O의 경우 사용자가 BeginRead를 호출할 수 있습니다. 주 스레드는 다른 작업을 계속 할 수 있으며 나중에 사용자가 데이터를 처리할 수 있습니다. 또한 여러 개의 I/O 요청을 동시에 보류할 수도 있습니다.
이 데이터를 사용 가능할 수 있게 되는 경우 알림을 받으려면 사용자가 보낸 I/O 요청에 해당하는 IAsyncResult를 전달하는 EndRead 또는 EndWrite를 호출하면 됩니다. 또한 EndRead 또는 EndWrite를 호출하는 콜백 메서드를 제공하여 읽거나 쓴 바이트 수를 확인할 수도 있습니다. 다수의 I/O 요청이 동시에 보류 중인 경우 비동기 I/O를 사용하면 향상된 성능을 제공할 수 있지만 일반적으로 응용 프로그램의 일부 중요한 구조를 바꾸어야만 제대로 작동합니다.
Stream 클래스를 사용하면 운영 체제에서 허용되는지 여부에 관계없이 동일한 스트림에서 동기식 읽기/쓰기와 비동기식 읽기/쓰기를 혼합하여 사용할 수 있습니다. Stream을 사용하는 경우 읽기/쓰기 작업을 동기식으로 구현하면 해당 작업의 비동기식 구현이 기본적으로 제공되며, 비동기식으로 구현하면 동기식 구현이 기본적으로 제공됩니다.
Stream에서 파생된 클래스를 구현하는 경우 동기 또는 비동기 Read 및 Write 메서드를 구현할 수 있어야 합니다. Read와 Write를 재정의할 수 있고 비동기 메서드(BeginRead, EndRead, BeginWrite 및 EndWrite)가 동기 메서드 구현과 함께 기본적으로 구현되지만 성능면에서는 효율성이 크지 않습니다. 마찬가지로 비동기 메서드를 구현하면 동기 메서드 Read와 Write가 구현되지만 일반적으로 동기 메서드를 직접 구현하면 성능이 향상됩니다. ReadByte 및 WriteByte의 기본 구현에서는 요소가 하나인 바이트 배열을 사용하여 동기 메서드 Read 및 Write를 호출합니다. Stream에서 클래스를 파생하는 경우 내부 바이트 버퍼가 있으면 성능 향상을 위해 내부 버퍼에 액세스하도록 이러한 메서드를 재정의하는 것이 좋습니다.
백업 저장소에 연결되는 스트림은 동기 또는 비동기 Read 및 Write 메서드를 재정의하여 서로 상반되는 비동기 또는 동기 작업을 기본적으로 수행할 수 있습니다. 스트림에서 비동기 또는 동기 연산을 지원하지 않는 경우에는 적절한 메서드를 사용하여 예외를 throw하는 것만 구현하면 됩니다.
다음 예제에서는 가상의 벌크 이미지 프로세서를 비동기식으로 구현하고 그 다음에는 동기식 구현 예제를 보여 줍니다. 이 코드는 디렉터리의 모든 파일에 대해 CPU를 많이 사용하는 작업을 수행하기 위해 디자인되었습니다. 자세한 내용은 비동기 프로그래밍 디자인 패턴 항목을 참조하십시오.
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
<SecurityPermissionAttribute(SecurityAction.Demand, Flags:=SecurityPermissionFlag.UnmanagedCode)> _
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 << 20)) As Byte
Dim i As Integer
For i = 0 To bytes.Length - 1
bytes(i) = 0
Next i
bytes = Nothing
GC.Collect()
Thread.Sleep(2000)
End Sub
Sub Main(ByVal args() As String)
Console.WriteLine("Bulk image processing sample application," + " using asynchronous IO")
Console.WriteLine("Simulates applying a simple " + "transformation to {0} ""images""", numImages)
Console.WriteLine("(Async FileStream & Threadpool benchmark)")
Console.WriteLine("Warning - this test requires {0} " + "bytes of temporary space", numPixels * numImages * 2)
If args.Length = 1 Then
processImageRepeats = Int32.Parse(args(0))
Console.WriteLine("ProcessImage inner loop - {0}.", processImageRepeats)
End If
MakeImageFiles()
TryToClearDiskCache()
ProcessImagesInBulk()
Cleanup()
End Sub
<DllImport("KERNEL32", SetLastError:=True)> _
Sub FlushFileBuffers(ByVal handle As IntPtr)
End Sub
End Module
using System;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Security.Permissions;
public class BulkImageProcAsync
{
public const String ImageBaseName = "tmpImage-";
public const int numImages = 200;
public const int numPixels = 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.
public static int processImageRepeats = 20;
// Threads must decrement NumImagesToFinish, and protect
// their access to it through a mutex.
public static int NumImagesToFinish = numImages;
public static Object[] NumImagesMutex = new Object[0];
// WaitObject is signalled when all image processing is done.
public static Object[] WaitObject = new Object[0];
public class ImageStateObject
{
public byte[] pixels;
public int imageNum;
public FileStream fs;
}
[SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)]
public static void MakeImageFiles()
{
int sides = (int)Math.Sqrt(numPixels);
Console.Write("Making {0} {1}x{1} images... ", numImages,
sides);
byte[] pixels = new byte[numPixels];
int i;
for (i = 0; i < numPixels; i++)
pixels[i] = (byte)i;
FileStream fs;
for (i = 0; i < numImages; i++)
{
fs = new FileStream(ImageBaseName + i + ".tmp",
FileMode.Create, FileAccess.Write, FileShare.None,
8192, false);
fs.Write(pixels, 0, pixels.Length);
FlushFileBuffers(fs.SafeFileHandle.DangerousGetHandle());
fs.Close();
}
fs = null;
Console.WriteLine("Done.");
}
public static void ReadInImageCallback(IAsyncResult asyncResult)
{
ImageStateObject state = (ImageStateObject)asyncResult.AsyncState;
Stream stream = state.fs;
int bytesRead = stream.EndRead(asyncResult);
if (bytesRead != numPixels)
throw new Exception(String.Format
("In ReadInImageCallback, got the wrong number of " +
"bytes from the image: {0}.", bytesRead));
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.
FileStream fs = new FileStream(ImageBaseName + state.imageNum +
".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 = null;
fs = null;
// Record that an image is finished now.
lock (NumImagesMutex)
{
NumImagesToFinish--;
if (NumImagesToFinish == 0)
{
Monitor.Enter(WaitObject);
Monitor.Pulse(WaitObject);
Monitor.Exit(WaitObject);
}
}
}
public static void ProcessImage(byte[] pixels, int imageNum)
{
Console.WriteLine("ProcessImage {0}", imageNum);
int y;
// Perform some CPU-intensive operation on the image.
for (int x = 0; x < processImageRepeats; x += 1)
for (y = 0; y < numPixels; y += 1)
pixels[y] += 1;
Console.WriteLine("ProcessImage {0} done.", imageNum);
}
public static void ProcessImagesInBulk()
{
Console.WriteLine("Processing images... ");
long t0 = Environment.TickCount;
NumImagesToFinish = numImages;
AsyncCallback readImageCallback = new
AsyncCallback(ReadInImageCallback);
for (int i = 0; i < numImages; i++)
{
ImageStateObject state = 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.
FileStream fs = new FileStream(ImageBaseName + i + ".tmp",
FileMode.Open, FileAccess.Read, FileShare.Read, 1, true);
state.fs = fs;
fs.BeginRead(state.pixels, 0, numPixels, readImageCallback,
state);
}
// Determine whether all images are done being processed.
// If not, block until all are finished.
bool mustBlock = false;
lock (NumImagesMutex)
{
if (NumImagesToFinish > 0)
mustBlock = true;
}
if (mustBlock)
{
Console.WriteLine("All worker threads are queued. " +
" Blocking until they complete. numLeft: {0}",
NumImagesToFinish);
Monitor.Enter(WaitObject);
Monitor.Wait(WaitObject);
Monitor.Exit(WaitObject);
}
long t1 = Environment.TickCount;
Console.WriteLine("Total time processing images: {0}ms",
(t1 - t0));
}
public static void Cleanup()
{
for (int i = 0; i < numImages; i++)
{
File.Delete(ImageBaseName + i + ".tmp");
File.Delete(ImageBaseName + i + ".done");
}
}
public static void TryToClearDiskCache()
{
// Try to force all pending writes to disk, and clear the
// disk cache of any data.
byte[] bytes = new byte[100 * (1 << 20)];
for (int i = 0; i < bytes.Length; i++)
bytes[i] = 0;
bytes = null;
GC.Collect();
Thread.Sleep(2000);
}
public static void Main(String[] args)
{
Console.WriteLine("Bulk image processing sample application," +
" using asynchronous IO");
Console.WriteLine("Simulates applying a simple " +
"transformation to {0} \"images\"", numImages);
Console.WriteLine("(Async FileStream & Threadpool benchmark)");
Console.WriteLine("Warning - this test requires {0} " +
"bytes of temporary space", (numPixels * numImages * 2));
if (args.Length == 1)
{
processImageRepeats = Int32.Parse(args[0]);
Console.WriteLine("ProcessImage inner loop - {0}.",
processImageRepeats);
}
MakeImageFiles();
TryToClearDiskCache();
ProcessImagesInBulk();
Cleanup();
}
[DllImport("KERNEL32", SetLastError = true)]
private static extern void FlushFileBuffers(IntPtr handle);
}
다음은 동기식 구현 예제입니다.
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
<SecurityPermissionAttribute(SecurityAction.Demand, Flags:=SecurityPermissionFlag.UnmanagedCode)> _
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 << 20)) As Byte
Dim i As Integer
For i = 0 To bytes.Length - 1
bytes(i) = 0
Next i
bytes = Nothing
GC.Collect()
Thread.Sleep(2000)
End Sub
Sub Main(ByVal args() As String)
Console.WriteLine("Bulk image processing sample application," + " using synchronous I/O.")
Console.WriteLine("Simulates applying a simple " + "transformation to {0} ""images.""", numImages)
Console.WriteLine("(ie, Sync FileStream benchmark).")
Console.WriteLine("Warning - this test requires {0} " + "bytes of temporary space", numPixels * numImages * 2)
If args.Length = 1 Then
processImageRepeats = Int32.Parse(args(0))
Console.WriteLine("ProcessImage inner loop {0}", processImageRepeats)
End If
MakeImageFiles()
TryToClearDiskCache()
ProcessImagesInBulk()
Cleanup()
End Sub
<DllImport("KERNEL32", SetLastError:=True)> _
Sub FlushFileBuffers(ByVal handle As IntPtr)
End Sub
End Module
using System;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Security.Permissions;
public class BulkImageProcSync
{
public const String ImageBaseName = "tmpImage-";
public const int numImages = 200;
public const int numPixels = 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.
public static int processImageRepeats = 20;
[SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)]
public static void MakeImageFiles()
{
int sides = (int)Math.Sqrt(numPixels);
Console.Write("Making {0} {1}x{1} images... ", numImages,
sides);
byte[] pixels = new byte[numPixels];
int i;
for (i = 0; i < numPixels; i++)
pixels[i] = (byte)i;
FileStream fs;
for (i = 0; i < numImages; i++)
{
fs = new FileStream(ImageBaseName + i + ".tmp",
FileMode.Create, FileAccess.Write, FileShare.None,
8192, false);
fs.Write(pixels, 0, pixels.Length);
FlushFileBuffers(fs.SafeFileHandle.DangerousGetHandle());
fs.Close();
}
fs = null;
Console.WriteLine("Done.");
}
public static void ProcessImage(byte[] pixels, int imageNum)
{
Console.WriteLine("ProcessImage {0}", imageNum);
int y;
// Perform some CPU-intensive operation on the image.
for (int x = 0; x < processImageRepeats; x += 1)
for (y = 0; y < numPixels; y += 1)
pixels[y] += 1;
Console.WriteLine("ProcessImage {0} done.", imageNum);
}
public static void ProcessImagesInBulk()
{
Console.WriteLine("Processing images... ");
long t0 = Environment.TickCount;
byte[] pixels = new byte[numPixels];
FileStream input;
FileStream output;
for (int i = 0; i < numImages; i++)
{
input = new FileStream(ImageBaseName + i + ".tmp",
FileMode.Open, FileAccess.Read, FileShare.Read,
4196, false);
input.Read(pixels, 0, numPixels);
input.Close();
ProcessImage(pixels, i);
output = new FileStream(ImageBaseName + i + ".done",
FileMode.Create, FileAccess.Write, FileShare.None,
4196, false);
output.Write(pixels, 0, numPixels);
output.Close();
}
input = null;
output = null;
long t1 = Environment.TickCount;
Console.WriteLine("Total time processing images: {0}ms",
(t1 - t0));
}
public static void Cleanup()
{
for (int i = 0; i < numImages; i++)
{
File.Delete(ImageBaseName + i + ".tmp");
File.Delete(ImageBaseName + i + ".done");
}
}
public static void TryToClearDiskCache()
{
byte[] bytes = new byte[100 * (1 << 20)];
for (int i = 0; i < bytes.Length; i++)
bytes[i] = 0;
bytes = null;
GC.Collect();
Thread.Sleep(2000);
}
public static void Main(String[] args)
{
Console.WriteLine("Bulk image processing sample application," +
" using synchronous I/O.");
Console.WriteLine("Simulates applying a simple " +
"transformation to {0} \"images.\"", numImages);
Console.WriteLine("(ie, Sync FileStream benchmark).");
Console.WriteLine("Warning - this test requires {0} " +
"bytes of temporary space", (numPixels * numImages * 2));
if (args.Length == 1)
{
processImageRepeats = Int32.Parse(args[0]);
Console.WriteLine("ProcessImage inner loop {0}",
processImageRepeats);
}
MakeImageFiles();
TryToClearDiskCache();
ProcessImagesInBulk();
Cleanup();
}
[DllImport("KERNEL32", SetLastError = true)]
private static extern void FlushFileBuffers(IntPtr handle);
}