虽然你没有这么说,但在我看来,`ProcessFrame 是一个函数,只要相机想要通知你有一个新的帧可供处理,就会调用它。
显然处理这个新帧需要相当长的时间。甚至可能在前一张图像尚未处理时已经抓取了一张新图像。
async-await 不会帮助您:您必须尽可能快地获取图像并命令另一个线程来处理获取的帧。您应该尽快从事件处理程序返回,最好是在处理完帧之前。另一个可能的要求是抓取的帧应该按照抓取的顺序进行处理和显示。
下面我会告诉你更多关于 async-await 的信息。首先,我会针对您的相机问题提出一个解决方案。
您的问题的解决方案在producer-consumer design pattern。生产者生产必须由消费者处理的数据。数据的产生速度可能比消费者处理的速度快或慢。如果在消费者处理之前生产的数据之前有新的数据可用,生产者应该将生产的数据保存在某个地方并继续生产。
只要消费者处理了生产数据,它就会检查是否有更多生产数据并开始处理。
这种情况一直持续到生产者通知消费者不再生成数据为止。
这种生产-消费者模式在MSDN Task Parallel Library (TPL) 中实现了所有多线程安全。这可以作为nuget package: Microsoft Tpl Dataflow 下载
您需要两个线程:生产者和消费者。制作人尽可能快地生成图像。生成的图像保存在BufferBlock<Frame>。
不同的线程将消耗生成的图像帧。
// the buffer to save frames that need to be processed:
private readonly BufferBlock<ImageFrame> buffer = new BufferBlock<ImageFrame>();
// event handler to be called whenever the camera has an image
// similar like your ProcessFrame
public async void OnImageAvailableAsync(object sender, EventArgs e)
{
// the sender is your camera who reports that an image can be grabbed:
ImageGrabber imageGrabber = (ImageGrabber)sender;
// grab the image:
ImageFrame grabbedFrame = imageGrabber.QueryFrame();
// save it on the buffer for processing:
await this.buffer.SendAsync(grabbedFrame);
// finished producing the image frame
}
消费者:
// this task will process grabbed images that are in the buffer
// until there are no more images to process
public async Task ProcessGrabbedImagesAsync()
{
// wait for data in the buffer
// stop waiting if no data is expected anymore
while (await buffer.OutpubAvailableAsync())
{
// The producer put some data in the buffer.
// Fetch it and process it.
// This may take some time, which is no problem. If the producer has new frames
// they will be saved in the buffer
FaceDetection.FaceDetectionFromFrame(imageFrame); // Face detection
var form = FormFaceDetection.Current;
form.scanPictureBox.Image = imageFrame.Bitmap;
faceList.Add(FaceRecognition.AnalyzeFace(imageFrame.Bitmap));
}
}
用法:
// Start a consumer task:
Task taskConsumer = task.Run( () => ProcessGrabbedImagesAsync());
// subscribe to the camera's event:
camera.EventImageAvailable += OnImageAvailableAsync;
camera.StartImageGrabbing();
// free to do other things
您需要停止图像抓取的语句
camera.StopImageGrabbing();
// unsubscribe:
camera.EventImageAvailable -= OnImageAvailableAsync;
// notify that no images will be produced:
buffer.Complete();
// await until the consumer is finished processing all produced images:
await taskConsumer;
关于异步等待
async-await 仅在您的进程必须空闲等待某个其他进程完成时才有意义,例如当它等待数据库查询完成、要写入的文件或要从中获取某些信息时互联网。在此期间,您的进程通常会闲置等待,直到其他进程完成。
使函数异步:
- 声明它是异步的
- 返回
Task而不是void和Task<TResult>而不是TResult
- 唯一的例外:事件处理程序返回 void 而不是任务:没有人等待事件处理程序完成
- 在您的异步函数中调用其他异步函数。
- 如果您还不需要异步函数的结果,但可以在异步函数完成之前执行其他操作。不要等待它,记住返回的
Task
-
await 返回的 Task<TResult> 就在您需要异步函数的结果之前。
-
await Task<TResult>的返回是TResult; await Task 返回无效。
- 良好做法:创建异步函数和非异步函数
乍一看,制作您的流程似乎可以解决您的问题。
private async void ProcessFrameAsync(object sender, EventArgs e)
{ // async event handlers return void instead of Task
var grabbedImage = await camera.FetchImageAsync();
// or if your camera has no async function:
await Task.Run( () => camera.FetchImage());
// this might be a length process:
ProcessImaged(grabbedImage);
ShowImage(grabbedImage);
}
如果在前一个图像完全处理之前有新图像可用,则再次调用事件处理程序。如果第二张图片的处理速度快于第一张图片的处理速度,那么它会在第一张图片显示之前显示。
此外,您还必须注意这两个过程不会相互干扰。
因此,在您的情况下,使事件处理程序异步不是一个好主意。
仅当您确定事件在下一个事件引发之前完成时才使事件处理程序异步
如果图像抓取不是通过事件完成,而是直接通过向相机请求新图像,async-await 会有所帮助:
async Task GrabAndProcessImages(CancellationToken token)
{
// grab the first image:
var grabbedImage = await camera.GrabImageAsync(token);
while (!token.CancellationRequested)
{
// start grabbing the next image, do not wait for it yet
var taskGrabImage = camera.GrabImageAsync(token);
// because I'm not awaiting, I'm free to do other things
// like processing the last grabbed image:
ProcessImage(grabbedImage);
// await for the next image:
grabbedImage = await taskGrabImage;
}
}
用法:
using(var cancellationTokenSource = new cancellationTokenSource())
{
Task taskProcessImages = grabAndProcessImages(cancellationTokenSource.Token);
// because I did not await, I'm free to do other things,
DoSomeThingElse();
// To stop grabbing images: cancel the cancellationTokenSource:
cancellationTokenSource.Cancel();
// or if you want to be sure that it grabbed for at least 30 seconds:
cancellationTokeSource.CanceAfter(TimeSpan.FromSeconds(30));
// still free to do something else,
DoSomeThingElse();
// before returning: await until the image grabbing task completes:
await taskProcessImages;
// if here, you are certain that processing images is completed
}