我正在使用C#(.NET 4.0)开发一个WPF项目,以从需要保存到磁盘(BMP格式)的高速摄像头捕获300个视频帧序列。视频帧需要以接近精确的时间间隔捕获,因此我无法在捕获帧时将帧保存到磁盘 - 磁盘I / O是不可预测的,它会丢弃帧之间的时间间隔。捕获卡有大约60个帧缓冲区。
我不确定实施此问题的解决方案的最佳方法是什么。我最初的想法是创建一个“BufferToDisk”线程,它在帧缓冲区可用时保存它们的图像。在这种情况下,主线程捕获帧缓冲区,然后通知线程以指示可以保存帧。问题是帧的捕获速度比线程可以保存文件的速度快,因此需要进行某种同步来处理这个问题。我在想,信号量对于这项工作来说是一个很好的工具。但是,我从未以这种方式使用信号量,所以我不确定如何继续。
这是解决这个问题的合理方法吗?如果是这样,有人会发布一些代码让我开始吗?
非常感谢任何帮助。
编辑: 在查看链接的“C#中的线程 - 第2部分”一书摘录后,我决定通过调整“ProducerConsumerQueue”类示例来实现该解决方案。这是我改编的代码:
class ProducerConsumerQueue : IDisposable
{
EventWaitHandle _wh = new AutoResetEvent(false);
Thread _worker;
readonly object _locker = new object();
Queue<string> _tasks = new Queue<string>();
public ProducerConsumerQueue()
{
_worker = new Thread(Work);
_worker.Start();
}
public void EnqueueTask(string task)
{
lock (_locker) _tasks.Enqueue(task);
_wh.Set();
}
public void Dispose()
{
EnqueueTask(null); // Signal the consumer to exit.
_worker.Join(); // Wait for the consumer's thread to finish.
_wh.Close(); // Release any OS resources.
}
void Work()
{
while (true)
{
string task = null;
lock (_locker)
if (_tasks.Count > 0)
{
task = _tasks.Dequeue();
if (task == null)
{
return;
}
}
if (task != null)
{
// parse the parameters from the input queue item
string[] indexVals = task.Split(',');
int frameNum = Convert.ToInt32(indexVals[0]);
int fileNum = Convert.ToInt32(indexVals[1]);
string path = indexVals[2];
// build the file name
string newFileName = String.Format("img{0:d3}.bmp", fileNum);
string fqfn = System.IO.Path.Combine(path, newFileName);
// save the captured image to disk
int ret = pxd_saveBmp(1, fqfn, frameNum, 0, 0, -1, -1, 0, 0);
}
else
{
_wh.WaitOne(); // No more tasks - wait for a signal
}
}
}
}
在主程序中使用该类:
// capture bitmap images and save them to disk
using (ProducerConsumerQueue q = new ProducerConsumerQueue())
{
for (int i = 0; i < 300; i++)
{
if (curFrmBuf > numFrmBufs)
{
curFrmBuf = 1; // wrap around to the first frame buffer
}
// snap an image to the image buffer
int ret = pxd_doSnap(1, curFrmBuf, 0);
// build the parameters for saving the frame to image file (for the queue)
string fileSaveParams = curFrmBuf + "," + (i + 1) + "," + newPath;
q.EnqueueTask(fileSaveParams);
curFrmBuf++;
}
}
非常漂亮的课程 - 此功能的少量代码。
非常感谢你们提出的建议。
答案 0 :(得分:1)
当然,听起来很合理。您可以使用信号量或其他线程同步原语。这听起来像是标准的生产者/消费者问题。查看一些伪代码here
答案 1 :(得分:1)
如果磁盘速度太慢(例如某些其他进程固定)60帧缓冲区不够,会发生什么?也许你需要一个BufferToMemory
和BufferToDisk
线程或某种组合。您希望主线程(捕获到缓冲区)具有最高优先级,BufferToMemory中,BufferToDisk最低。
无论如何,回到Semaphores,我建议你阅读:http://www.albahari.com/threading/part2.aspx#_Semaphore。信号量应该为你做,但我会推荐SemaphoreSlim
(.NET 4)。
答案 2 :(得分:1)
由于您将此视为生产者/消费者问题(根据您对@ siz的答案的回复判断),您可能希望查看专为此类情景设计的BlockingCollection<T>
。
它允许任意数量的生产者线程将数据推送到集合中,并允许任意数量的消费者线程再次将其拉出。在这种情况下,您可能只需要一个生产者和一个消费者线程。
BlockingCollection<T>
完成所有工作,确保消费者线程只有在生成线程表示还有更多工作要做时才会唤醒并处理。它还负责允许建立一个工作队列。