2010-10-10 24 views
9

Tôi cần đọc một luồng hai lần, từ đầu đến cuối.Tại sao xử lý StreamReader khiến luồng không thể đọc được?

Nhưng mã sau đây ném một ngoại lệ ObjectDisposedException: Cannot access a closed file.

string fileToReadPath = @"<path here>"; 
using (FileStream fs = new FileStream(fileToReadPath, FileMode.Open)) 
{ 
    using (StreamReader reader = new StreamReader(fs)) 
    { 
     string text = reader.ReadToEnd(); 
     Console.WriteLine(text); 
    } 

    fs.Seek(0, SeekOrigin.Begin); // ObjectDisposedException thrown. 

    using (StreamReader reader = new StreamReader(fs)) 
    { 
     string text = reader.ReadToEnd(); 
     Console.WriteLine(text); 
    } 
} 

Tại sao nó lại xảy ra? Điều gì thực sự được xử lý? Và tại sao thao tác StreamReader ảnh hưởng đến luồng được liên kết theo cách này? Không phải là nó hợp lý để mong đợi rằng một dòng có thể tìm kiếm có thể được đọc nhiều lần, bao gồm bởi một số StreamReader s?

+1

Cũng xem xét sử dụng System.IO.File.ReadAllText() trong các tình huống như thế này. Nó đơn giản hơn. –

+0

@Dave Markle: bạn nói đúng. Tôi đã đặt nó như một ví dụ ngắn. Trên thực tế, trong mã thực, các luồng mà tôi xử lý có thể rất lớn, vì vậy đầu đọc đầu tiên đọc chúng trên mỗi dòng, sau đó luồng được sao chép trên mỗi byte sang luồng khác. –

Trả lời

12

Điều này xảy ra do việc giữ StreamReader 'quyền sở hữu' của luồng. Nói cách khác, nó tự chịu trách nhiệm đóng luồng nguồn. Ngay sau khi chương trình của bạn gọi số Dispose hoặc Close (để lại phạm vi tuyên bố using trong trường hợp của bạn) thì nó cũng sẽ xử lý luồng nguồn. Gọi số fs.Dispose() trong trường hợp của bạn. Vì vậy, luồng tệp đã chết sau khi rời khối using đầu tiên. Đó là hành vi nhất quán, tất cả các lớp dòng trong .NET bao bọc một luồng khác hoạt động theo cách này.

Có một hàm tạo cho StreamReader cho phép nói rằng nó không sở hữu luồng nguồn. Tuy nhiên nó không thể truy cập được từ một chương trình .NET, hàm tạo là nội bộ.

Trong trường hợp cụ thể này, bạn sẽ giải quyết vấn đề bằng cách không sử dụng mã số using cho số StreamReader. Tuy nhiên đó là một chi tiết thực hiện khá lông. Có chắc chắn là một giải pháp tốt hơn có sẵn cho bạn nhưng mã quá tổng hợp để đề xuất một thực tế.

7

Mục đích của Dispose() là xóa tài nguyên khi bạn kết thúc luồng. Lý do trình đọc tác động đến luồng là do trình đọc chỉ lọc luồng và do đó việc xử lý người đọc không có ý nghĩa ngoại trừ trong ngữ cảnh chuỗi cuộc gọi cũng đến luồng nguồn.

Để khắc phục mã của bạn, chỉ cần sử dụng một đầu đọc toàn bộ thời gian:

using (FileStream fs = new FileStream(fileToReadPath, FileMode.Open)) 
using (StreamReader reader = new StreamReader(fs)) 
{ 
    string text = reader.ReadToEnd(); 
    Console.WriteLine(text); 

    fs.Seek(0, SeekOrigin.Begin); // ObjectDisposedException not thrown now 

    text = reader.ReadToEnd(); 
    Console.WriteLine(text); 
} 

Edited để giải quyết ý kiến ​​dưới đây:

Trong hầu hết các trường hợp, bạn không cần phải truy cập vào dòng cơ bản như bạn làm trong mã của bạn (fs.Seek). Trong những trường hợp này, thực tế là StreamReader chuỗi cuộc gọi của nó đến luồng cơ bản cho phép bạn tiết kiệm tiền trên mã bằng cách không sử dụng câu lệnh usings cho luồng. Ví dụ, đoạn code sẽ như thế nào:

using (StreamReader reader = new StreamReader(new FileStream(fileToReadPath, FileMode.Open))) 
{ 
    ... 
} 
+0

Thực tế, tôi hỏi tại sao Dispose() trên đầu đọc * ảnh hưởng đến luồng *. Tôi không hiểu câu trả lời của bạn. Nó có nghĩa là độc giả Vứt bỏ chỉ có một mục đích để làm sạch dữ liệu luồng? Vậy tại sao ngay cả 'StreamReader' thực hiện' IDisposable', nếu nó không làm bất cứ điều gì * hữu ích * (vì việc xử lý chính luồng đó sẽ trong mọi trường hợp làm tất cả công việc)? –

+1

@MainMa, mục đích của Dispose() là * luôn luôn * để đảm bảo tất cả các tài nguyên được liên kết với 'IDisposable' đã cho được làm sạch. Vì trình đọc và luồng đại diện cho cùng một thực thể xử lý một đối tượng có cùng tác dụng như việc xử lý đối tượng kia. –

2

Using định nghĩa một phạm vi, bên ngoài trong đó một đối tượng sẽ được xử lý, do đó ObjectDisposedException. Bạn không thể truy cập nội dung của StreamReader bên ngoài khối này.

0

Dispose() trên cha mẹ sẽ Dispose() tất cả các luồng được sở hữu. Thật không may, các luồng không có phương thức Detach(), vì vậy bạn phải tạo một số giải pháp thay thế tại đây.

0

Tôi không biết tại sao, nhưng bạn có thể khiến StreamReader của bạn không thể hiểu được. Bằng cách đó, luồng cơ bản của bạn sẽ không được xử lý, ngay cả khi StreamReader đã được thu thập.

+0

Tất nhiên. Nhưng nó không phải là quá trực quan để không đặt một sử dụng cho một 'StreamReader' cho tôi, và nó có thể sẽ vi phạm các quy tắc FxCop. Nhân tiện, vấn đề ở nguồn gốc của câu hỏi này xuất hiện khi tôi tái cấu trúc và mã cũ, nơi mà các bài viết đã hoàn toàn mất tích. –

1

Tôi đồng ý với câu hỏi của bạn. Vấn đề lớn nhất với tác dụng phụ có chủ ý này là khi các nhà phát triển không biết về nó và bị mù quáng theo "thực hành tốt nhất" xung quanh một StreamReader với using. Nhưng nó có thể gây ra một số thực sự khó khăn để theo dõi lỗi khi nó là về sở hữu một đối tượng tồn tại lâu dài của, là tốt nhất (tồi tệ nhất?) Ví dụ: Tôi đã nhìn thấy là

using (var sr = new StreamReader(HttpContext.Current.Request.InputStream)) 
{ 
    body = sr.ReadToEnd(); 
} 

Các nhà phát triển không hề biết InputStream tại là hosed cho bất kỳ nơi nào trong tương lai mà hy vọng nó sẽ ở đó.

Rõ ràng, một khi bạn biết nội bộ bạn biết để tránh using và chỉ đọc và đặt lại vị trí. Nhưng tôi nghĩ nguyên tắc cốt lõi của thiết kế API là tránh các tác dụng phụ, đặc biệt là không phá hủy dữ liệu bạn đang hành động. Không có gì vốn có về một lớp được cho là một "người đọc" nên xóa dữ liệu mà nó đọc khi thực hiện "sử dụng" nó. Việc xử lý người đọc sẽ giải phóng mọi tham chiếu đến Luồng, không tự xóa luồng. Điều duy nhất tôi có thể nghĩ là sự lựa chọn phải được thực hiện vì người đọc đang thay đổi trạng thái bên trong khác của Stream, giống như vị trí của con trỏ tìm kiếm, mà chúng giả định nếu bạn đang bao quanh một xung quanh nó. được thực hiện với mọi thứ. Mặt khác, giống như trong ví dụ của bạn, nếu bạn đang tạo một Luồng, chính luồng đó sẽ ở trong một số using, nhưng nếu bạn đang đọc Luồng đã được tạo bên ngoài phương pháp ngay lập tức của bạn, nó sẽ là mã xóa dữ liệu.

Những gì tôi làm và nói với các nhà phát triển của chúng tôi để làm trên trường Suối rằng mã đọc không tạo ra một cách rõ ràng là ...

// save position before reading 
long position = theStream.Position; 
theStream.Seek(0, SeekOrigin.Begin); 
// DO NOT put this StreamReader in a using, StreamReader.Dispose() clears the stream 
StreamReader sr = new StreamReader(theStream); 
string content = sr.ReadToEnd(); 
theStream.Seek(position, SeekOrigin.Begin); 

(xin lỗi tôi thêm này như là một câu trả lời, sẽ không phù hợp trong một bình luận, tôi rất thích thảo luận thêm về quyết định thiết kế này của khung công tác này)

+2

Một thiết kế tốt hơn cho một StreamReader sẽ có được một đối số constructor xác định xem nó có nên sở hữu luồng không. Có rất nhiều tình huống mà mã có thể mở một luồng, tạo một trình đọc cho nó, đưa người đọc đến một cái gì đó mà sẽ đọc dữ liệu vào một thời điểm trong tương lai, và sau đó không còn sử dụng nữa cho luồng. Trong tình huống đó, luồng sẽ được xử lý khi người đọc hoàn thành nó, nhưng người sáng tạo luồng có thể không có ý tưởng khi nào. – supercat

Các vấn đề liên quan