main(string[] args) {
string file = #"D:\123.txt";
using (FileStream f = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
f.Position = 0;
StartReadBookThread(f);
}
}
//this is a public API for user
public static Thread StartReadBookThread(Stream stream)
{
Console.WriteLine("CanSeek:" + stream.CanSeek);
var t = new Thread(() => DoReadBook(stream));
t.Start();
return t;
}
private static void DoReadBook(Stream stream)
{
Console.WriteLine("CanSeek:" + stream.CanSeek);
}
In the DoReadBook method,the stream becomes closed, CanSeek becomes false.
How to handle it if I want to read the stream in a thread
the general way is we provide a public API StartReadBookThread for user and it run in a thread .
user we always like call the parameter file stream in the suggest using(){} ... way.
so how can we read the file in a better way.
you know we just do it in a thread,sometimes it may get closed.
Your stream is disposed when StartReadBookThread returns and you hit the } in your using statement in main.
Your workflow is basically:
Buy a lawn mower (FileStream).
Ask a worker to mow the lawn (Thread).
Immediately return the lawn mower (using statement).
Expect the worker to keep on mowing the lawn with a lawn mower you no longer have (wrong expectations).
using (FileStream f = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var stream = new MemoryStream();
f.Position = 0;
f.CopyTo(stream);
StartReadBookThread(stream );
}
I'd like this code
U need to wait on thread you are returning from function StartReadBookThread
Have look at updated code snippet.
main(string[] args)
{
string file = #"D:\123.txt";
using (FileStream f = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
f.Position = 0;
var t = StartReadBookThread(f);
t.Join(); // wait on the thread
}
}
private static Thread StartReadBookThread(Stream stream)
{
Console.WriteLine("CanSeek:" + stream.CanSeek);
var t = new Thread(() => DoReadBook(stream));
t.Start();
return t;
}
Related
I'm creating an endpoint that returning File download after it generates an Excel file, I have 2 methods, the first one is to return FileStream object as asynchronous and the second one is to return File download which called from Http.
Many said I have to make the stream seek to the beginning again before it's read by FileResult, but it seems doesn't work.
First method:
private async Task<FileStream> Generate(int projectId, DateTime period)
{
...
if (...)
{
using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
...
return fs;
}
}
return null;
}
Second method:
[HttpPost]
public async Task<IActionResult> Index([FromBody]ReportFilter filter)
{
FileStream fs = await Generate(filter.projectId, DateTime.Parse(filter.period));
if (fs != null)
{
fs.Seek(0, SeekOrigin.Begin);
return File(fs, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "kpi.xlsx");
}
return Json(new { status="error", message="Error while processing request" });
}
Unfortunately, it throws:
System.ObjectDisposedException: Cannot access a closed file.
at System.IO.FileStream.Seek(Int64 offset, SeekOrigin origin)
[UPDATE]
Without using block:
private async Task<FileStream> Generate(int projectId, DateTime period)
{
...
if (...)
{
FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write);
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet1 = workbook.CreateSheet("Sheet1");
sheet1.AddMergedRegion(new CellRangeAddress(0, 0, 0, 10));
var rowIndex = 0;
IRow row = sheet1.CreateRow(rowIndex);
row.Height = 30 * 80;
var cell = row.CreateCell(0);
var font = workbook.CreateFont();
font.IsBold = true;
font.Color = HSSFColor.DarkBlue.Index2;
cell.CellStyle.SetFont(font);
cell.SetCellValue("A very long piece of text that I want to auto-fit innit, yeah. Although if it gets really, really long it'll probably start messing up more.");
sheet1.AutoSizeColumn(0);
rowIndex++;
workbook.Write(fs);
return fs;
}
return null;
}
[UPDATE]
Using jalsh's suggestion (by reopening the FileStream while preparing a download):
if (System.IO.File.Exists(filename))
{
FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
return File(fs, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "kpi.xlsx");
}
Often when you want to use a disposable object like this it is better to inject in the action required, rather that to expose the disposable object outside of the method that creates it.
I've simplified your code down, but this is the basic idea:
private async Task Generate(int projectId, DateTime period, Action<FileStream> operation)
{
using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
operation(fs);
}
}
Now you call it like this:
public async Task Index(int projectId, string period)
{
await Generate(projectId, DateTime.Parse(period), fs =>
{
if (fs != null)
{
fs.Seek(0, SeekOrigin.Begin);
return File(fs, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "kpi.xlsx");
}
});
}
This allows operation(fs) to complete before the end of the using statement.
The using statement calls dispose() at the end of its scope. see MSDN link, it has a case just like yours...
you could either not use the using statement and dispose of the object manually whenever you're done with it. Or you could just reopen the filestream again, or maybe return a File Instance from your method that you could reopen the stream with
Now that you shared your full code, it seems to me that your Write call is disposing the FileStream or closing it, you can try reopen the filestream just after you do the Write() call.
I have a multithreading application which write to the same file on a specific event .
how can i lock the file and make the thread wait until its free ?
i can't use FileStream since it will throw exception on the other threads (can't acces)
FileStream fileStream = new FileStream(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
is there any proper way to do this ?
You need a thread-safe filewriter to manage threads. You can use ReaderWriterLockSlim to perform it.
public class FileWriter
{
private static ReaderWriterLockSlim lock_ = new ReaderWriterLockSlim();
public void WriteData(string dataWh,string filePath)
{
lock_.EnterWriteLock();
try
{
using (var fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
byte[] dataAsByteArray = new UTF8Encoding(true).GetBytes(dataWh);
fs.Write(dataAsByteArray, 0, dataWh.Length);
}
}
finally
{
lock_.ExitWriteLock();
}
}
}
Example;
Parallel.For(0, 100, new ParallelOptions { MaxDegreeOfParallelism = 10 },i =>
{
new FileWriter().WriteData("Sample Data", Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"SampleFile.txt"));
});
I'm using a filestream to access a file (making an md5 ComputeHash). If I attempt to rename the file during this time (which fails as the file is being accessed). So far so good, but when I then try to open the file anew after the original filestream is closed I get the info that the file is open in another process.
Code:
using (Stream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
MD5 md5 = MD5.Create();
byte[] mymd5computed = md5.ComputeHash(fileStream);
......
}
Thread.Sleep(50);
Thread a = new Thread (()=>{(FileStream sourceStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){....} });
Like I said if while during the computation of the MD5 I try to rename the file I get the info that the file is still locked.
The lock on a file sometimes isn't released right away when you close your stream, so there are some other solutions you can use to wait until you can access the file again. One of them is explained here: http://www.codeproject.com/Tips/164428/C-FileStream-Lock-How-to-wait-for-a-file-to-get-re.
Recap:
public static void Lock(string path, Action<FileStream> action) {
var autoResetEvent = new AutoResetEvent(false);
while(true)
{
try
{
using (var file = File.Open(path,
FileMode.OpenOrCreate,
FileAccess.ReadWrite,
FileShare.Write))
{
action(file);
break;
}
}
catch (IOException)
{
var fileSystemWatcher =
new FileSystemWatcher(Path.GetDirectoryName(path))
{
EnableRaisingEvents = true
};
fileSystemWatcher.Changed +=
(o, e) =>
{
if(Path.GetFullPath(e.FullPath) == Path.GetFullPath(path))
{
autoResetEvent.Set();
}
};
autoResetEvent.WaitOne();
}
}
}
Sample use:
Lock(#"c:\file",
(f) =>
{
try
{
f.Write(buf, 0, buf.Length);
}
catch(IOException ioe)
{
// handle IOException
}
});
Hope it helps! :)
I was using this code to redirect my console output to file and then read and display it. I want to go away from using files because I'm polluting my folders with those console files. How can I do this in memory ? I don't want any files to pollute the system. Maybe I'm trying something weird here. I just want 1 thread to read the console output of the very same application:
1 application
multiple threads write to console
1 thread reads from console
My working file code:
private StreamWriter currentOut = null;
private void RedirectConsole()
{
currentOut = new StreamWriter(new FileStream(filename,
FileMode.Create, FileAccess.Write, FileShare.Read));
currentOut.AutoFlush = true;
Console.SetOut(currentOut);
ThreadPool.QueueUserWorkItem(o => { Listen(); });
}
private void Listen()
{
StreamReader fileIn = new StreamReader(new FileStream(filename,
FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
while (true)
{
try
{
if (!fileIn.EndOfStream)
{
string a = fileIn.ReadLine();
MessageBox.Show(a);
}
Thread.Sleep(25);
}
catch { }
}
}
This seems to be what I want. But I'm unable to implement that (help?). File is like a buffer. You write to it from one end and read from another. I need the same in memory.
Try:
private StreamWriter currentOut = null;
private MemoryStream ms = new MemoryStream();
private void RedirectConsole()
{
currentOut = new StreamWriter(ms);
currentOut.AutoFlush = true;
Console.SetOut(currentOut);
ThreadPool.QueueUserWorkItem(o => { Listen(); });
}
private void Listen()
{
StreamReader fileIn = new StreamReader(ms);
// ...
}
The problem with using MemoryStream is that the read position advances with the write position. Pipes (System.IO.Pipes namespace) are a better choice for use as temporary buffers where the read position needs to advance independent of the write position. Admittedly, this more or less does exactly what your working solution does, though it removes the need to implement the buffer yourself.
class ConsoleRedirector : IDisposable
{
private TextWriter originalOut = Console.Out;
private AnonymousPipeServerStream consoleOutServerPipe;
private StreamWriter currentOut;
public ConsoleRedirector()
{
this.consoleOutServerPipe = new AnonymousPipeServerStream(PipeDirection.Out);
this.currentOut = new StreamWriter(this.consoleOutServerPipe);
this.currentOut.AutoFlush = true;
Console.SetOut(this.currentOut);
ThreadPool.QueueUserWorkItem(o => { this.Listen(); });
}
private void Listen()
{
AnonymousPipeClientStream consoleOutClientPipe = new AnonymousPipeClientStream(PipeDirection.In, this.consoleOutServerPipe.ClientSafePipeHandle);
using (StreamReader fileIn = new StreamReader(consoleOutClientPipe))
{
// ...
}
}
public void Dispose()
{
this.currentOut.Dispose();
Console.SetOut(this.originalOut);
}
}
I ended up writing a derived stream class and replaced the FileStream with my own stream. I probably should have avoided that. But since I couldn't find a working solution, it was also a good practice. Something like this:
public class MyStream: Stream
{
private byte[] internalBuffer = new byte[4096];
// ...
public override int Read(byte[] buffer, int offset, int count)
{
// used by StreamReader
}
public override void Write(byte[] buffer, int offset, int count)
{
// used by StreamWriter
}
}
override all the other stuff, handle multi-threading while enlarging internalBuffer and disposing passed data.
I have a thread producing lines in a log file:
var t1 = Task.Factory.StartNew(() =>
{
using (var fileStream = File.Open(file, FileMode.Create, FileAccess.Write, FileShare.Read))
using (var streamWriter = new StreamWriter(fileStream))
{
for (var i = 0; i < 10; i++)
{
streamWriter.WriteLine(i);
streamWriter.Flush();
Thread.Sleep(1000);
}
}
File.Delete(file);
});
And I have a thread reading lines from the same log file:
// Reads lines from the log file.
var t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(500); // Horrible wait to ensure file existence in this test case.
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read | FileShare.Write))
using (var streamReader = new StreamReader(fileStream))
{
string line;
while ((line = streamReader.ReadLine()) != null) Console.WriteLine(line);
// FIXME: The stream reader stops, instead of doing a continous read.
Console.WriteLine("End of file");
}
});
The reader is supposed to read all written lines, therefore it should wait for more data instead of stopping at the first time it encounters EOF. I do not mind if the reader is never 'finished', so long as the file continues to exist, the reader is allowed to continue reading. How can I achieve this? Full code for reproduction purposes:
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace PlayGround
{
internal static class Program
{
private static void Main()
{
const string file = "test.log";
// Writes lines to the log file.
var t1 = Task.Factory.StartNew(() =>
{
using (var fileStream = File.Open(file, FileMode.Create, FileAccess.Write, FileShare.Read))
using (var streamWriter = new StreamWriter(fileStream))
{
for (var i = 0; i < 10; i++)
{
streamWriter.WriteLine(i);
streamWriter.Flush();
Thread.Sleep(1000);
}
}
File.Delete(file);
});
// Reads lines from the log file.
var t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(500); // Horrible wait to ensure file existence in this test case.
using (
var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read,
FileShare.Delete | FileShare.Read | FileShare.Write))
using (var streamReader = new StreamReader(fileStream))
{
string line;
while ((line = streamReader.ReadLine()) != null) Console.WriteLine(line);
// FIXME: The stream reader stops, instead of doing a continous read.
Console.WriteLine("End of file");
}
});
Task.WaitAll(t1, t2);
}
}
}
EDIT: As a practical example, this is useful for a scenario where a third party process is producing log entries which need to be read and processed. You could see this as a log file reader if that makes the application and use clearer.
You could perform a wait when the line == null, by checking the streamReader.EndOfFile property. Using Thread.Sleep(1000) is not an ideal solution, a bit hacky, and I guess there are other better alternative solutions out there. :-)
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read | FileShare.Write))
using (var streamReader = new StreamReader(fileStream))
{
string line;
bool running = true; // we may want to terminate this loop in some condition.
while (running)
{
line = streamReader.ReadLine();
if (line != null)
{
Console.WriteLine(line);
}
else // as per edit, the whole else block can be omitted.
{
while (streamReader.EndOfStream)
{
Thread.Sleep(1000); // wait around for n time. This could end up in an infinte loop if the file is not written to anymore.
}
}
}
// FIXME: The stream reader stops, instead of doing a continous read.
Console.WriteLine("End of file");
}
EDIT: You can do without the else block:
else
{
while (streamReader.EndOfStream)
{
Thread.Sleep(1000)
}
}
Like this:
while (running)
{
line = streamReader.ReadLine();
if (line != null)
{
Console.WriteLine(line);
}
}
You need synchronization mechanism, in this case I use AutoResetEvent.
The required changes based on your code is.
const string file = "test.log";
// Adds new line.
AutoResetEvent signal = new AutoResetEvent(false);
streamWriter.Flush();
// Adds new line.
signal.Set();
File.Delete(file);
// Adds new line
signal.Set();
Thread.Sleep(500);
// Replaces with.
signal.WaitOne();
while ((line = streamReader.ReadLine()) != null) Console.WriteLine(line);
// Replaces with.
while ((line = streamReader.ReadLine()) != null)
{
signal.WaitOne();
Console.WriteLine(line);
}
Full code
const string file = "test.log";
AutoResetEvent signal = new AutoResetEvent(false);
// Writes lines to the log file.
var t1 = Task.Factory.StartNew(() =>
{
using (var fileStream = File.Open(file, FileMode.Create, FileAccess.Write, FileShare.Read))
{
using (var streamWriter = new StreamWriter(fileStream))
{
for (var i = 0; i < 10; i++)
{
streamWriter.WriteLine(i);
streamWriter.Flush();
signal.Set();
Thread.Sleep(10);
}
}
}
File.Delete(file);
signal.Set();
});
// Reads lines from the log file.
var t2 = Task.Factory.StartNew(() =>
{
signal.WaitOne();
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read | FileShare.Write))
{
using (var streamReader = new StreamReader(fileStream))
{
string line;
while ((line = streamReader.ReadLine()) != null)
{
signal.WaitOne();
Console.WriteLine(line);
}
// FIXME: The stream reader stops, instead of doing a continous read.
Console.WriteLine("End of file");
}
}
});
Task.WaitAll(t1, t2);