c# - una - ¿Cómo guardo los archivos en el disco duro en un hilo separado?
guardar un archivo en una ruta especifica c# (6)
Tengo una cámara y estoy leyendo las imágenes en tiempo real en una matriz. Estoy aplicando algún algoritmo a la imagen y mostrándola. Luego obtengo la siguiente imagen y la visualizo también. Así que estoy transmitiendo imágenes de la cámara a la pantalla. Sin embargo, también quiero guardar imágenes en el disco duro una vez que las haya mostrado. Intenté usar el hilo principal pero todo se ralentizó demasiado. Luego intenté usar ThreadPool (ver código a continuación). Esto no ralentiza la visualización, pero he encontrado que las imágenes no se están guardando correctamente. Parece que no están en el orden esperado y después de que se han guardado unas 50 imágenes, los datos de imagen subsiguientes parecen distorsionados. Supongo que se están iniciando demasiados hilos.
¿Hay una mejor manera de hacer esto? Creo que solo necesito un hilo para guardar las imágenes. Tal vez algún tipo de cola que guarda cada imagen secuencialmente. Siempre que se haga en segundo plano y no ralentice la pantalla. Si alguien pudiera publicar un fragmento de código sería fantástico.
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) { }
}
Debe comprobar antes de pensar en los subprocesos si la velocidad de un disco normal será suficiente para su tarea, ya que puede crear imágenes más rápido que escribir en el disco. Si la creación de imágenes es más rápida que la escritura, lo vería con un disco de memoria, pero luego debe calcular si el tamaño es suficiente hasta que detenga la cámara, de modo que pueda escribir en el disco normal durante la noche.
Si usa .NET 4.0, le sugiero que use la cola concurrente junto con un hilo normal (ya que el hilo se ejecutará hasta que el programa finalice).
Debe crear un búfer distinto para el hilo desde el que leer datos, de lo contrario, el hilo principal lo sobreescribirá cuando lo descargue en un archivo. La forma en que lo hace parece copiar solo referencias ( image1
en particular).
Asi que:
ThreadPool.QueueUserWorkItem(WriteImageToFile, data);
en lugar de datos, enviará una copia profunda de los data
. Como parece que ya lo está haciendo, pero en el hilo de trabajo, solo necesita mover la copia antes de enviarla.
HTH
Creo que deberías evitar comenzar un nuevo hilo para cada imagen en particular. Como tiene un solo disco duro y almacena todos los archivos en el único directorio, debe usar solo un hilo de escritor de disco. Entonces, recomendaría utilizar una cola concurrente para transferir trabajos desde el hilo de la cámara al hilo del escritor del disco. No muestro el "fragmento de código" porque no se puede escribir con buena calidad en unas líneas de código.
Además, definitivamente debe colocar en algún lugar ''nuevo corto [20000]'' para cada imagen, de lo contrario se sobrescribe con la imagen siguiente antes de guardarlo en el disco.
Además, esperaría que sea suficiente para escribir archivos en el hilo principal, porque Windows usa técnicas simultáneas (principalmente caché de disco) automáticamente cuando escribe datos en el disco. ¿Estás seguro de que tu hardware es lo suficientemente rápido como para escribir todos esos datos en tiempo real?
La manera rápida y sucia es comenzar un solo hilo nuevo y trabajar con miembros de la clase global: el nuevo hilo debe poder acceder a ellos, mientras que el hilo principal los actualizará.
En primer lugar, tenga estas líneas fuera de cualquier función:
private List<ImageData> arrGlobalData = new List<ImageData>();
private bool keepWritingImages = true;
Ahora cambie el código en el hilo "principal" a este:
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;
Y finalmente tiene esa función para el nuevo hilo:
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);
}
}
Puedes hacer lo siguiente.
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 });
}
}
Más tarde puedes consumirlo como.
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");
}
Al tratar con hilos, el orden ya no está bajo su control. El grupo de subprocesos puede elegir programar los subprocesos en el orden que desee. Si necesita que las cosas sucedan secuencialmente en un orden específico, el enhebrado no tiene mucho sentido de todos modos.
En cuanto a las imágenes dañadas, parece que se está pasando la instancia short[] image1
. No está claro qué sucede dentro de ReadImageFromCamera
, pero dado que se pasa una matriz preinicializada, es probable que el método use esa matriz y simplemente copie datos en ella (aunque la palabra clave ref
indica que podría crear una matriz nueva). instancia y asignar eso en su lugar). Luego pasa esa instancia de matriz a WriteImageToFile
en un hilo separado.
Mientras tanto, en paralelo, obtienes la siguiente imagen. Ahora tiene un escenario donde ReadImageFromCamera
podría escribir datos en la matriz al mismo tiempo que WriteImageToFile
almacena los datos en el disco. Ahí tienes tu imagen corrupta. Esto se puede evitar pasando una nueva instancia de matriz a 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);
Sin embargo, como ha mencionado Al Kepp , dado que solo tiene un disco duro, el lanzamiento de muchos subprocesos podría no ser su mejor opción aquí. Podría considerar tener un hilo separado de larga ejecución para almacenar datos en el disco y colocar las imágenes en algún tipo de cola del que el hilo de almacenamiento toma datos y escribe en el disco. Esto viene con su propio conjunto de problemas relacionados con la concurrencia, lo que limita el tamaño de la cola y lo que no.