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

C# 如何在单独的线程中将文件保存到硬盘?,c#,multithreading,background,save,C#,Multithreading,Background,Save,我有一个摄像头,我正在将图像实时读取到阵列中。 我正在对图像应用一些算法并显示它。然后我得到下一个图像并显示它。因此,我将图像从相机传输到显示器。不过,我也希望在显示图像后将其保存到硬盘。我试着使用主线程,但速度太慢了。 然后我尝试使用ThreadPool(参见下面的代码)。这不会减慢显示速度,但我发现图像保存不正确。看起来它们的顺序不符合预期,在保存了大约50张图像后,后续图像数据看起来乱七八糟。我猜启动的线程太多了 有更好的方法吗?我想我只需要一个线程来保存图像。可能是某种按顺序保存每个图像

我有一个摄像头,我正在将图像实时读取到阵列中。 我正在对图像应用一些算法并显示它。然后我得到下一个图像并显示它。因此,我将图像从相机传输到显示器。不过,我也希望在显示图像后将其保存到硬盘。我试着使用主线程,但速度太慢了。 然后我尝试使用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) { }
}

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

此外,您肯定必须为每个映像放置“new short[20000]”,否则在将其保存到磁盘之前,它会被下一个映像覆盖


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

您需要为线程创建一个不同的缓冲区,以便从中读取数据,否则当您将数据转储到文件时,主线程将覆盖它。您这样做似乎只是复制引用(尤其是
image1

因此:

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


HTH

在考虑线程之前,您必须检查正常磁盘的速度是否足以完成任务,因为创建映像的速度可能比写入磁盘的速度快。如果图像创建速度比写入速度快,我会考虑使用内存磁盘,但在停止相机之前,您需要计算大小是否足够,以便您可以在一夜之间写入正常磁盘。

如果您使用.NET 4.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); 
不过,由于您只有一个硬盘驱动器,因此启动多个线程可能不是您的最佳选择。您可以考虑使用一个长期运行的独立线程在磁盘上存储数据,并将图像放入某种队列中,存储线程从中提取数据并将其写入磁盘。这就带来了一系列处理并发性的问题,限制了队列的大小等等

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");
        }

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

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

private List<ImageData> arrGlobalData = new List<ImageData>();
private bool keepWritingImages = true;
最后为新线程提供这样的功能:

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);
    }
}

short[]image1=新的short[20000];我想你肯定你的图像不会占用超过40000字节。是将数据复制到你传递的数组中,还是创建一个新数组分配给
image1
?是的,很抱歉,这只是为了说明。根据用户设置,图像大小会有所不同(但在流媒体启动后不会发生变化)。我正在读取所有的图像数据,所以没有问题。嗨,这部分也不重要。我得到了图像数据,并将其放入一个short[]数组中。我的目标是将对
short
数组的引用传递到
WriteImageToFile
。如果
ReadImageFromCamera
将图像数据读取到同一个数组实例中,您将得到奇怪的结果。一个浅拷贝就足够了。由于元素是不可变的值类型,因此不能以损害克隆数组引用的方式修改它们。@Fredrik:浅拷贝就足够了+1但原因不是元素的不变性,因为数组作为一个整体不是不可变的。在这个场景中,它只是保持不变——它只显示并保存到磁盘,所以没有任何原因
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);
    }
}