Как сохранить файлы на жесткий диск в отдельном потоке? - PullRequest
6 голосов
/ 01 февраля 2011

У меня есть камера, и я читаю изображения в реальном времени в массив. Я применяю некоторый алгоритм к изображению и отображаю его. Затем я получаю следующее изображение и отображаю его. Поэтому я передаю изображения с камеры на дисплей. Однако я также хочу сохранить изображения на жесткий диск, как только я их отобразил. Я пытался использовать основной поток, но все слишком сильно замедлилось. Затем я попытался использовать 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 ]

6 голосов
/ 01 февраля 2011

Я думаю, вам следует избегать создания новой темы для каждого конкретного изображения. Поскольку у вас есть только один жесткий диск и все файлы хранятся в одном каталоге, вы должны использовать только один поток записи на диск. Тогда я бы рекомендовал использовать некоторую параллельную очередь для передачи заданий из потока камеры в поток записи на диск. Я не показываю «фрагмент кода», потому что это не то, что вы можете написать в хорошем качестве в несколько строк кода.

Также вы обязательно должны где-то поместить 'new short [20000]' для каждого изображения, в противном случае оно будет перезаписано следующим изображением, прежде чем вы сохраните его на диск.

Кроме того, я ожидал бы, что достаточно записать файлы в главном потоке, поскольку Windows автоматически использует параллельные методы (в основном дисковый кэш) при записи данных на диск. Вы уверены, что ваше оборудование достаточно быстрое для записи всех этих данных в режиме реального времени?

0 голосов
/ 01 февраля 2011

Быстрый и грязный способ - запуск одного нового потока и работа с глобальными членами класса - новый поток должен иметь доступ к ним, в то время как основной поток обновит их.

Прежде всего, используйте эти строки вне какой-либо функции:

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

Теперь измените код в "основном" потоке на этот:

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);
    }
}
0 голосов
/ 01 февраля 2011

Вы можете сделать следующее.

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");
        }
0 голосов
/ 01 февраля 2011

При работе с потоками порядок больше не находится под вашим контролем. Пул потоков может планировать потоки в любом порядке. Если вам нужно, чтобы все происходило последовательно в определенном порядке, многопоточность не имеет особого смысла.

Что касается поврежденных изображений, похоже, что экземпляр 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); 

Тем не менее, , как уже упоминал Аль Кепп , поскольку у вас только один жесткий диск, запуск нескольких потоков может оказаться не лучшим вариантом для вас. Можно было бы создать один длительный отдельный поток для хранения данных на диске и поместить образы в какую-то очередь, в которую поток хранения собирает данные и записывает их на диск. Это связано с собственным набором проблем, связанных с параллелизмом, ограничением размера очереди, а что нет.

0 голосов
/ 01 февраля 2011

Прежде чем думать о потоках, вы должны проверить, будет ли скорость обычного диска достаточной для вашей задачи, поскольку вы можете создавать образы быстрее, чем записывать на диск.Если создание образа происходит быстрее, чем запись, я бы посмотрел, используя диск памяти, но затем вам нужно рассчитать, достаточен ли размер до тех пор, пока вы не остановите камеру, чтобы вы могли записать на обычный диск в одночасье.
Если выиспользовать .NET 4.0 Я бы посоветовал вам использовать параллельную очередь вместе с обычным потоком (так как поток будет работать до завершения программы).

0 голосов
/ 01 февраля 2011

Вам необходимо создать отдельный буфер для потока, из которого будут считываться данные, в противном случае основной поток перезапишет его при выгрузке в файл.То, как вы это делаете, похоже, копирует только ссылки (в частности, image1).

Итак:

ThreadPool.QueueUserWorkItem(WriteImageToFile, data);

вместо данных, которые вы отправите в глубине копия data.Поскольку кажется, что вы уже делаете это - но в рабочем потоке - вам просто нужно переместить копию перед отправкой .

HTH

...