2009-10-05 6 views
6

Giả sử tôi muốn xác định lớp TempFileStream tạo tệp tạm thời bằng phương thức Path.GetTempFileName(). Tệp tạm thời phải bị xóa khi đối tượng của TempFileStream không còn cần thiết nữa, ví dụ: đóng cửa hoặc xử lý:Xử lý với luồng tệp tạm thời

class TempFileStream: FileStream 
{ 
    string m_TempFileName = Path.GetTempFileName(); 
    public TempFileStream(FileMode fileMode): base(m_TempFileName,fileMode) {} 

    /// ... 

public ovverride Dispose(bool disposing) 
{ 
    /// ??? 
} 

} 

Tôi nên thực hiện điều này một cách đơn giản và an toàn như thế nào?

+0

Bạn phải sử dụng FileStream cho điều này, bạn không thể sử dụng MemoryStream? Bằng cách đó bạn không phải xử lý tất cả các vấn đề có thể có liên quan đến việc xóa tệp. – armannvg

+0

@armannvg, bạn đang nói về vấn đề gì? Đó là một lưu trữ tạm thời cho các tập tin rất lớn trước khi nó được ghi vào cơ sở dữ liệu. – sh0gged

+0

Chỉ cần các tập tin bình thường xóa các vấn đề -> IOException, UnauthorizedAccessException vv Nhưng nếu bạn đang làm việc với một tập tin rất lớn, thì MemoryStream không phải là một lựa chọn – armannvg

Trả lời

2
base.Dispose(disposing); // disposes the base stream so the file is no longer used 
if (disposing) 
    File.Delete(m_TempFileName); // deletes the file 

Bạn nên thêm xử lý ngoại lệ phù hợp cho Tệp.Delete nếu cần.

0

Về cơ bản theo logic TempFileStream bạn luôn sử dụng tệp được tạo với tên duy nhất (đó là những gì Path.GetTempFileName làm) và bạn luôn xóa nó sau khi sử dụng nó. Vì vậy, không cần phải cung cấp hàm tạo chấp nhận FileMode vì bạn luôn sử dụng nó trong cùng một chế độ.

+0

Điểm tốt. :) Cảm ơn. – sh0gged

5

Đây là một ý tưởng thú vị, nhưng có điều gì đó về thiết kế này gây rắc rối cho tôi. Hãy tha thứ cho tôi nếu bạn đã giải quyết vấn đề này trong thiết kế của mình. Nhưng nếu thiết kế của bạn chỉ là một wrapper đơn giản xung quanh FileStream, có một sự tinh tế nhưng, tôi nghĩ rằng, vấn đề quan trọng.

Nếu bạn đang xóa tệp khi luồng được đóng, điều đó có nghĩa là cách duy nhất để thực sự sử dụng dữ liệu trong tệp là nếu FileAccessReadWrite. Chính xác? Nói cách khác, bạn sẽ sử dụng các tập tin với mã trông như thế này:

using (TempFileStream t as new TempFileStream()) 
{ 
    WriteDataToTempFile(t); 
    t.Seek(0, SeekOrigin.Begin); 
    ReadDataFromTempFile(t); 
} 

Vấn đề tôi thấy là ReadDataFromTempFile được mong đợi các tập tin được mở để truy cập đọc, không đọc/ghi truy cập. Và điều này mở ra cánh cửa cho một số lỗi mà tôi nghĩ sẽ rất khó tìm. Hãy xem xét mã như thế này:

using (TempFileStream t as new TempFileStream()) 
{ 
    MyClass o = new MyClass(o); 
    o.TempStream = t; 
    o.ProduceOutput(); 
    t.Seek(0, SeekOrigin.Begin); 
    o.ProcessOutput(); 
} 

... khi so sánh với điều này:

MyClass o = new MyClass(); 
string n = Path.GetTempFileName(); 
using (FileStream s = new FileStream(n, FileMode.Create, FileAccess.Write)) 
{ 
    o.TempStream = t; 
    o.ProduceOutput(); 
} 
using (FileStream s = new FileStream(n, FileMode.Open, FileAccess.Read)) 
{ 
    o.TempStream = t; 
    o.ProcessOutput(); 
} 
File.Delete(n); 

Chắc chắn, phương pháp đầu tiên là ngắn hơn so với thứ hai. Nhưng phương pháp thứ hai sẽ ném một ngoại lệ nếu ProcessOutput gọi một phương thức ghi đến TempStream. (Hoặc thiết lập một thuộc tính có accessor set đặt ra một sự kiện mà trình xử lý sự kiện gửi một cuộc gọi đến một phương thức ghi tới TempStream, cách mà vấn đề này có thể sẽ xảy ra.) Đầu tiên sẽ tạo ra các kết quả không mong muốn mà không có lý do rõ ràng.

Bạn có thể giải quyết vấn đề này, bằng cách yêu cầu lớp học TempFileStream mở lớp cơ bản FileStream bằng cách sử dụng FileAccess.Write. Sau đó, triển khai phương thức Rewind đóng FileStream này và tạo phương thức mới sử dụng FileAccess.Read. Nếu bạn làm điều đó, bất kỳ phương pháp nào cố gắng ghi vào tệp trong khi nó được mở để truy cập đọc (hoặc ngược lại) sẽ ít nhất là ném một ngoại lệ.

+0

Điểm tốt, cảm ơn rất nhiều. Tôi nên tính đến điều này. Ban đầu tôi nghĩ về một wrapper đơn giản. – sh0gged

26

Hãy thử này thay vì:

public class TempFileStream : FileStream 
{ 
    public TempFileStream() 
     : base(Path.GetTempFileName(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose) { } 
    public TempFileStream(FileAccess access) 
     : base(Path.GetTempFileName(), FileMode.Create, access, FileShare.Read, 4096, FileOptions.DeleteOnClose) { } 
    public TempFileStream(FileAccess access, FileShare share) 
     : base(Path.GetTempFileName(), FileMode.Create, access, share, 4096, FileOptions.DeleteOnClose) { } 
    public TempFileStream(FileAccess access, FileShare share, int bufferSize) 
     : base(Path.GetTempFileName(), FileMode.Create, access, share, bufferSize, FileOptions.DeleteOnClose) { } 
} 

Tùy chọn FileOptions.DeleteOnClose sẽ đảm bảo rằng hệ điều hành xóa các tập tin tạm thời tự động khi bạn đóng file. Không cần thiết cho một phương pháp vứt bỏ đặc biệt, bởi vì nó là tất cả được chăm sóc cho bạn.

+2

+1 cho DeleteOnClose, không biết điều đó, có ích! –

+2

+1, tôi thích bao nhiêu tôi học chỉ đọc câu trả lời của người khác trên trang web này. – shambulator

2

Tôi biết đây là một chuỗi cũ hơn, nhưng đây là giải pháp thay thế. Tôi bắt đầu thực hiện TempFileStream, nhưng tôi muốn đồng thời hơn.Trường hợp sử dụng của tôi liên quan đến việc xuất kết quả cơ sở dữ liệu [tiềm năng MB] thành tệp CSV qua MVC. Tôi muốn bắt đầu tải xuống máy khách ngay sau khi dữ liệu có sẵn từ truy vấn cơ sở dữ liệu thay vì đợi một tệp tạm thời có khả năng lớn được viết trước khi tôi bắt đầu tải xuống.

Trong giải pháp này, tôi khởi chạy truy vấn trong một chuỗi riêng biệt lấp đầy một AnonymousPipeStream. Các chủ đề chính sau đó có thể slurp dữ liệu từ đầu kia của ống như nó có sẵn. Nó sử dụng .Net 4 Nhiệm vụ.

Hy vọng người khác thấy điều này hữu ích.

-Rob

điều khiển phương pháp:

public FileResult ResultExport (ReportOptions options) 
{ 
    ResultExport rpt = new ResultExport(options); 
    HttpContext.Response.BufferOutput = false; 
    return File(rpt.Export(), "text/csv", "results.csv"); 
} 

mẫu:

public ResultExport 
{ 
    private AnonymousPipeServerStream WriteStream = null; 

    public Stream Export() 
    { 
     // 
     // We'll fire off the database query in a background 
     // thread. It will write data to one end of the pipe. We'll return the reader 
     // end of that pipe to our caller. 
     // 
     WriteStream = new AnonymousPipeServerStream(PipeDirection.Out); 
     AnonymousPipeClientStream reader = new AnonymousPipeClientStream(PipeDirection.In, WriteStream.ClientSafePipeHandle); 

     // 
     // Call Execute() in a background thread. 
     // 
     Task.Factory.StartNew(() => Execute()); 

     // 
     // While Execute() is filling the pipe with data, 
     // return the reader end of the pipe to our caller. 
     // 
     return reader; 
    } 

    private void Execute() 
    { 
     // 
     // WriteStream should only by populated by Export() 
     // 
     if(WriteStream != null) 
     { 
      using (StreamWriter sw = new StreamWriter(WriteStream, Encoding.UTF8, 4096)) 
      { 
       // 
       // Shove data into the StreamWriter as we get it from the database 
       // 
       foreach (string line in ExportCore()) 
       { 
        // Each line is a comma-delimited set of values 
        sw.WriteLine(line); 
       } 
      } 
     } 
    } 
}