如何在单独的线程中将文件保存到硬盘?

时间:2011-02-01 11:42:18

标签: c# multithreading background save

我有一台相机,我正在将图像实时读入阵列。 我正在将一些算法应用于图像并显示它。然后我得到下一个图像并显示它。所以我将图像从相机流式传输到显示器。但是,我也希望在显示图像后将图像保存到硬盘。我尝试使用主线程,但一切都放慢了太多。 然后我尝试使用ThreadPool(参见下面的代码)。这不会减慢显示速度,但我发现图像没有正确保存。看起来它们不是预期的顺序,并且在保存了大约50个图像之后,后续图像数据看起来很乱。我猜太多线程正在启​​动。

有更好的方法吗?我想我只需要一个线程来保存图像。也许是某种顺序保存每个图像的队列。只要它在后台完成并且不会减慢显示速度。如果有人可以发布一个很棒的代码片段。

short[] image1 = new short[20000];
while(streaming)
{
    ReadImageFromCamera(ref image1)
    ImageData data;    

    data.fileName = imageNumber;
    data.image = image1;

    ThreadPool.QueueUserWorkItem(WriteImageToFile, data);  // Send the writes to the queue
}


private void WriteImageToFile(object imageData) {

    try {
        ImageData data = (ImageData)imageData;
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

        string fName = myDirectory + @"/" + Convert.ToString(data.fileName) + @".spe";

        using (Stream myStream = new FileStream(fName, FileMode.Create)) {
            bf.Serialize(myStream, data.image);
        }
    }
    catch (Exception) { }
}

6 个答案:

答案 0 :(得分:6)

我认为你应该避免为每个特定的图像开始一个新的线程。由于您只有一个硬盘驱动器并将所有文件存储到单个目录中,因此您应该只使用一个磁盘写入器线程。然后我建议使用一些并发队列将作业从相机线程传输到磁盘写入器线程。我没有显示“代码片段”,因为这不是一个可以用几行代码以高质量编写的东西。

此外,您必须在某处为每个图像添加“新短[20000]”,否则在将其保存到磁盘之前会被下一个图像覆盖。

另外,我希望在主线程中写入文件就足够了,因为Windows在将数据写入磁盘时会自动使用并发技术(主要是磁盘缓存)。您确定您的硬件足够快以实时写入所有这些数据吗?

答案 1 :(得分:0)

您需要为线程创建一个不同的缓冲区来读取数据,否则主线程会在将其转储到文件时覆盖它。你这样做的方式似乎只复制引用(特别是image1)。

所以:

ThreadPool.QueueUserWorkItem(WriteImageToFile, data);

而不是您将在data副本中发送的数据。因为看起来你已经在做了 - 但是在工作线程中 - 你只需要在发送之前移动副本

HTH

答案 2 :(得分:0)

如果普通磁盘的速度足以完成任务,则必须在考虑线程之前进行检查,因为您可能比写入磁盘更快地创建映像。如果图像创建比写入更快,我会考虑使用内存盘,但是在你停止相机之前需要计算尺寸是否足够,以便你可以在一夜之间写入普通盘。 如果您使用.NET 4.0,我建议您将Concurrent queue与普通线程一起使用(因为线程将一直运行直到程序完成)。

答案 3 :(得分:0)

处理线程时,订单不再受您控制。线程池可以选择以任何顺序调度线程。如果你需要以特定的顺序按顺序发生事情,那么线程无论如何都没有多大意义。

关于损坏的图像,看起来传递short[] image1实例。目前还不清楚ReadImageFromCamera内部发生了什么,但是由于你将一个预先初始化的数组传递给它,很可能该方法将使用该数组并简单地将数据复制到其中(即使ref关键字指示它可能会创建一个全新的数组实例并转而分配它。然后在单独的线程上将该数组实例传递给WriteImageToFile

与此同时,您可以获得下一张图片。现在您有一个场景,ReadImageFromCamera可能会在WriteImageToFile将数据存储在磁盘上的同时将数据写入阵列。你有你的图像损坏。将新数组实例传递给WriteImageToFile

可以避免这种情况
ReadImageFromCamera(ref image1)
ImageData data;    

data.fileName = imageNumber;
data.image = (short[])image1.Clone(); // create a new array instance, so that
                                      // the next call to ReadImageFromCamera 
                                      // will not corrupt the data

ThreadPool.QueueUserWorkItem(WriteImageToFile, data); 

仍然是as has been mentioned by Al Kepp,因为你只有一个硬盘,启动多个线程可能不是你最好的选择。您可以考虑使用一个长时间运行的单独线程将数据存储在磁盘上,并将图像放入某种队列,存储线程从磁盘中获取数据并写入磁盘。这带来了一系列处理并发问题的问题,限制了队列的大小,以及什么不是。

答案 4 :(得分:0)

您可以执行以下操作。

public class AsyncFileWriter
    {
        private readonly FileStream fs;
        private readonly AsyncCallback callback;
        public Action FinishedCallback;
        private IAsyncResult result;
        private class AsyncState
        {
            public FileStream Fs;

        }

        private void WriteCore(IAsyncResult ar)
        {
            if (result != null)
            {
                FileStream stream = ((AsyncState)ar.AsyncState).Fs;
                stream.EndWrite(result);
                if (this.FinishedCallback != null)
                {
                    FinishedCallback();
                }
            }
        }

        public AsyncFileWriter(FileStream fs, Action finishNotification)
        {
            this.fs = fs;
            callback = new AsyncCallback(WriteCore);
            this.FinishedCallback = finishNotification;
        }

        public AsyncFileWriter(FileStream fs)
            : this(fs, null)
        {

        }


        public void Write(Byte[] data)
        {
            result = fs.BeginWrite(data, 0, data.Length, callback, new AsyncState() { Fs = fs });
        }
    }

稍后您可以将其用作。

static void Main(string[] args)
        {
            FileStream fs = File.Create("D:\\ror.txt");
            ManualResetEvent evt = new ManualResetEvent(false);
            AsyncFileWriter writer = new AsyncFileWriter(fs, () =>
                                                                 {
                                                                     Console.Write("Write Finished");
                                                                     evt.Set();
                                                                 }

                );
            byte[] bytes = File.ReadAllBytes("D:\\test.xml");//Getting some random bytes

            writer.Write(bytes);
            evt.WaitOne();
            Console.Write("Write Done");
        }

答案 5 :(得分:0)

快速而肮脏的方式是启动单个新线程并使用全局类成员 - 新线程应该能够访问它们,而主线程将更新它们。

首先,将这些行置于任何函数之外:

private List<ImageData> arrGlobalData = new List<ImageData>();
private bool keepWritingImages = true;

现在将“main”主题中的代码更改为:

short[] image1 = new short[20000];
ThreadPool.QueueUserWorkItem(WriteImageToFile, null);
while(streaming)
{
    ReadImageFromCamera(ref image1)
    ImageData data = new ImageData();
    data.fileName = imageNumber;
    data.image = image1;
    arrGlobalData.Add(data);
}
keepWritingImages = false;

最后为新线程提供了这样的功能:

private void WriteImageToFile(object imageData)
{
    while (keepWritingImages)
    {
        if (arrGlobalData.Count > 0)
        {
            ImageData data = arrGlobalData[0];
            try
            {
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                string fName = myDirectory + @"/" + Convert.ToString(data.fileName) + @".spe";
                using (Stream myStream = new FileStream(fName, FileMode.Create))
                {
                    bf.Serialize(myStream, data.image);
                }
            }
            catch
            {
            }
            finally
            {
                arrGlobalData.Remove(data);
            }
        }

        Thread.Sleep(10);
    }
}