I/O限制性异步操作
1. CLR异步编程模型
2. IO限制性异步操作中异常处理
3. 最佳实践
<1>. CLR异步编程模型
.net中基本上包含两种异步编程模式:使用IAsyncResult对象,或者是使用事件的异步操作.这里主要是关于使用IAsyncResult的异步操作. 在。net中存在的大量已有类型能够支持异步编程,一般会提供BeginOperation和EndOperation的匹配方法,在BeginOperation中提供回调函数,在异步操作完成之后被调用,同时包含一个state参数,该参数是一个用户自定义参数 ,此对象用来向异步异步操作完成之后调用的方法传递的应用程序的特定信息状态,在io读取的通常情况下,该state参数是读取数据的缓存区,这样在异步操作 完成时,CLR将读取的数据存放在该state变量中,如下。
上面是异步操作的调用方,一般情况下回调函数将包含一个IAsyncResult参数, 通过该参数可以得到异步操作的结果。IAsyncResult定义在System命名空间下,结构如下:
public interface IAsyncResult
{
// 得到在异步代用时用户自定义state参数
object AsyncState { get; }
// 同步对象
WaitHandle AsyncWaitHandle { get; }
// 该异步操作是否已经完成
bool CompletedSynchronously { get; }
bool IsCompleted { get; }
}
{
// 得到在异步代用时用户自定义state参数
object AsyncState { get; }
// 同步对象
WaitHandle AsyncWaitHandle { get; }
// 该异步操作是否已经完成
bool CompletedSynchronously { get; }
bool IsCompleted { get; }
}
下面是一个MSDN上的例子:
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);
}
整个程序执行流程如下,通过这里可以看出异步的io操作的话,简单的说还是一种共享内存的形式,这里是共享buffer,异步编程的最主要挑战(个人观点是 如何设计共享数据buffer):
整个程序执行流程如下,通过这里可以看出异步的io操作的话,简单的说还是一种共享内存的形式,这里是共享buffer,异步编程的最主要挑战(个人观点是 如何设计共享数据buffer):
<2>. IO限制性异步操作中异常处理
异常操作可能发生在异步操作的任何时间.如果是在调用Begin抛出的异常的话,线程池将不会调用任何传递给BeginXXX的回调方法。如果异常是在io读取的过程中发生的话,那么CLR将在调用EndXXX的方法中抛出异常.所以需要调用EndXXX方法。
<3>. 最佳实践
3.1 总是调用EndXXX方法,因为在异步调用的过程中可能出现异常.
3.2 不能取消异步io操作
3.3 APM的效率其实还是很浪费内存
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?