2010-04-08 29 views
8

Ứng dụng web của tôi trả về tệp từ hệ thống tệp. Những tệp này là động, vì vậy tôi không có cách nào để biết tên của chúng. Khi tệp này không tồn tại, ứng dụng sẽ tạo nó từ cơ sở dữ liệu. Tôi muốn tránh rằng hai luồng khác nhau tạo lại cùng một tệp cùng một lúc hoặc một chuỗi cố gắng trả về tệp trong khi luồng khác đang tạo tệp đó.Cách khóa tệp và tránh đọc trong khi đang viết

Ngoài ra, tôi không muốn bị khóa trên phần tử phổ biến cho tất cả các tệp. Vì vậy, tôi nên khóa tập tin chỉ khi tôi đang tạo nó.

Vì vậy, tôi muốn khóa tệp cho đến khi quá trình giải trí hoàn tất, nếu chủ đề khác cố gắng truy cập vào nó ... nó sẽ phải đợi tệp được mở khóa.

Tôi đã đọc về FileStream.Lock, nhưng tôi phải biết độ dài tệp và nó sẽ không ngăn chặn chủ đề khác cố gắng đọc tệp, vì vậy nó không hoạt động cho trường hợp cụ thể của tôi.

Tôi cũng đã đọc về FileShare.None, nhưng nó sẽ ném một ngoại lệ (loại ngoại lệ?) Nếu chuỗi/quá trình khác cố gắng truy cập tệp ... vì vậy tôi nên phát triển "thử lại trong khi "bởi vì tôi muốn tránh thế hệ ngoại lệ ... và tôi không thích quá nhiều cách tiếp cận đó, mặc dù có thể không có cách nào tốt hơn.

Cách tiếp cận với FileShare.None sẽ này nhiều hay ít:

static void Main(string[] args) 
    { 
     new Thread(new ThreadStart(WriteFile)).Start(); 
     Thread.Sleep(1000); 
     new Thread(new ThreadStart(ReadFile)).Start(); 

     Console.ReadKey(true); 
    } 

    static void WriteFile() 
    { 
     using (FileStream fs = new FileStream("lala.txt", FileMode.Create, FileAccess.Write, FileShare.None)) 
     using (StreamWriter sw = new StreamWriter(fs)) 
     { 
      Thread.Sleep(3000); 
      sw.WriteLine("trolololoooooooooo lolololo"); 
     } 
    } 

    static void ReadFile() 
    { 
     Boolean readed = false; 
     Int32 maxTries = 5; 

     while (!readed && maxTries > 0) 
     { 
      try 
      { 
       Console.WriteLine("Reading..."); 
       using (FileStream fs = new FileStream("lala.txt", FileMode.Open, FileAccess.Read, FileShare.Read)) 
       using (StreamReader sr = new StreamReader(fs)) 
       { 
        while (!sr.EndOfStream) 
         Console.WriteLine(sr.ReadToEnd()); 
       } 
       readed = true; 
       Console.WriteLine("Readed"); 
      } 
      catch (IOException) 
      { 
       Console.WriteLine("Fail: " + maxTries.ToString()); 
       maxTries--; 
       Thread.Sleep(1000); 
      } 
     } 
    } 

Nhưng tôi không thích thực tế là tôi phải bắt ngoại lệ, hãy thử nhiều lần và chờ đợi một khoản tiền không chính xác của thời gian: |

+2

FileShare.None thay vì FileAccess.None (FileAccess xác định quyền truy cập ứng dụng của bạn, trong khi FileShare được sử dụng để khóa tệp, như bạn muốn) – jpabluz

+2

Về chủ đề khác, hãy nhớ mở khóa tệp bất cứ khi nào ứng dụng của bạn bị phá hủy , Tôi ghét khi khóa vẫn còn sau đó. – jpabluz

+0

Tôi đã chỉnh sửa và sửa nó, cảm ơn! – vtortola

Trả lời

3

Bạn có thể xử lý điều này bằng cách sử dụng đối số FileMode.CreateNew cho hàm tạo luồng. Một trong những chủ đề sẽ mất và tìm ra rằng tập tin đã được tạo ra một micro giây trước đó bởi một chủ đề khác. Và sẽ nhận được một IOException.

Sau đó, nó sẽ cần quay, chờ tệp được tạo hoàn toàn. Mà bạn thực thi với FileShare.None. Bắt ngoại lệ ở đây không quan trọng, nó vẫn đang quay. Không có cách nào khác cho nó trừ khi bạn P/Invoke.

+0

Có vẻ như bạn đã đúng, không có cách giải quyết khác cho việc này. Cảm ơn! – vtortola

1

tôi nghĩ rằng một aproach đúng sẽ là như sau: tạo ra một tập hợp các chuỗi được u sẽ lưu tên tập tin hiện nên một thread sẽ xử lý các tập tin vào thời điểm, một cái gì đó như thế này

//somewhere on your code or put on a singleton 
static System.Collections.Generic.HashSet<String> filesAlreadyProcessed= new System.Collections.Generic.HashSet<String>(); 


//thread main method code 
bool filealreadyprocessed = false 
lock(filesAlreadyProcessed){ 
    if(set.Contains(filename)){ 
    filealreadyprocessed= true; 
    } 
    else{ 
    set.Add(filename) 
    } 
} 
if(!filealreadyprocessed){ 
//ProcessFile 
} 
+0

Đó là vấn đề, mà tôi không muốn khóa một yếu tố chung cho tất cả các tập tin. Đầu tiên, có được một khóa là tốn kém, và tôi không muốn có được một khóa cho mỗi cuộc gọi yêu cầu cho một tập tin, bất kể nếu tập tin đã tồn tại hay không.Thứ hai tôi không muốn chặn các chủ đề đang cố gắng để có được một tập tin khác nhau bởi vì tôi đang tạo ra một trong số họ. Vì những lý do này, tôi muốn khóa chính tệp đó. Chúc mừng. – vtortola

+0

Bạn đã đo thời gian để có được một khóa và chặn cho đến khi hoàn thành so với thời gian cho các chủ đề thức dậy, kiểm tra truy cập, nhận được một ngoại lệ, ngủ, và lặp đi lặp lại nhiều lần? Tôi hy vọng một chiến lược khóa sẽ trở nên hấp dẫn hơn ở đây. 'Thread.Sleep' là ít mong muốn để chặn trên một khóa. Điều gì sẽ xảy ra nếu quá trình ghi kết thúc sớm? Chủ đề đọc không thức dậy. Bạn có thể muốn xem xét một 'ManualResetEvent' để kiểm soát truy cập giữa hai luồng. –

1

Bạn có cách nào để xác định tệp nào đang được tạo không?

Giả sử mỗi một tệp trong số đó tương ứng với một ID duy nhất trong cơ sở dữ liệu của bạn. Bạn tạo một vị trí tập trung (Singleton?), Nơi các ID này có thể được liên kết với một cái gì đó có thể khóa (Từ điển). Một chuỗi cần đọc/ghi vào một trong các tệp đó thực hiện như sau:

//Request access 
ReaderWriterLockSlim fileLock = null; 
bool needCreate = false; 
lock(Coordination.Instance) 
{ 
    if(Coordination.Instance.ContainsKey(theId)) 
    { 
     fileLock = Coordination.Instance[theId]; 
    } 
    else if(!fileExists(theId)) //check if the file exists at this moment 
    { 
     Coordination.Instance[theId] = fileLock = new ReaderWriterLockSlim(); 
     fileLock.EnterWriteLock(); //give no other thread the chance to get into write mode 
     needCreate = true; 
    } 
    else 
    { 
     //The file exists, and whoever created it, is done with writing. No need to synchronize in this case. 
    } 
} 

if(needCreate) 
{ 
    createFile(theId); //Writes the file from the database 
    lock(Coordination.Instance) 
     Coordination.Instance.Remove[theId]; 
    fileLock.ExitWriteLock(); 
    fileLock = null; 
} 

if(fileLock != null) 
    fileLock.EnterReadLock(); 

//read your data from the file 

if(fileLock != null) 
    fileLock.ExitReadLock(); 

Tất nhiên, các chủ đề không tuân theo giao thức khóa chính xác này sẽ có quyền truy cập vào tệp.

Bây giờ, khóa trên một đối tượng Singleton chắc chắn không phải là lý tưởng, nhưng nếu ứng dụng của bạn cần đồng bộ hóa toàn cầu thì đây là một cách để đạt được nó.

+0

Cùng một vấn đề mà mã @hworangdo, trong mỗi yêu cầu bạn phải mua khóa, ngay cả khi bạn không cần nó. – vtortola

+1

@vtortola: Đúng. Trong việc bảo vệ câu trả lời của tôi: Bắt một khóa không đắt, (đo nó, nó thực sự không có gì, đặc biệt là so với tập tin IO) nhưng chờ đợi một thread để giải phóng khóa là. Bạn có thể thử tìm một cài đặt từ điển không có khóa. Bạn chỉ cần cẩn thận trong trường hợp tệp cần được tạo, để chỉ một luồng được giao nhiệm vụ tạo ra nó. –

+0

Có lẽ bạn đang đúng, tôi chưa bao giờ thử nghiệm mức độ phóng to như thế nào khi tự mình mua một chiếc khóa, tôi biết điều đó vì tôi đã đọc nó. Việc đợi một chuỗi khác để giải phóng khóa là tốn kém hơn, nhưng nó sẽ chỉ xảy ra một lần cho mỗi tập tin. Tôi sẽ kiểm tra cách tiếp cận của bạn sau này, có thể là của bạn nhanh hơn. Cảm ơn! – vtortola

1

Câu hỏi của bạn thực sự khiến tôi suy nghĩ.

Thay vì có mọi luồng chịu trách nhiệm truy cập tệp và chặn chúng, nếu bạn sử dụng hàng đợi tệp cần phải được duy trì và có một chuỗi công việc nền đơn lẻ và vẫn tồn tại?

Trong khi nhân viên nền đang quay mặt đi, bạn có thể có các luồng ứng dụng web trả về giá trị db cho đến khi tệp thực sự tồn tại.

Tôi đã đăng rất đơn giản example of this on GitHub.

Hãy thoải mái chụp ảnh và cho tôi biết suy nghĩ của bạn.

FYI, nếu bạn không có git, bạn có thể sử dụng svn để kéo nó http://svn.github.com/statianzo/MultiThreadFileAccessWebApp

0

Tại sao bạn không chỉ sử dụng cơ sở dữ liệu - ví dụ nếu bạn có cách liên kết tên tệp với dữ liệu từ db nó chứa, chỉ cần thêm một số thông tin vào db xác định liệu tệp có tồn tại với thông tin đó hiện tại và khi nó được tạo, cách thông tin cũ trong tệp là ... Khi một thread cần một số thông tin, nó sẽ kiểm tra db để xem liệu tệp đó có tồn tại không và nếu không, nó viết ra một hàng vào bảng nói rằng nó tạo ra tệp. Khi nó được thực hiện nó cập nhật hàng với một boolean nói rằng các tập tin đã sẵn sàng để được sử dụng bởi những người khác.

điều tốt đẹp về phương pháp này - tất cả thông tin của bạn ở một nơi - vì vậy bạn có thể khôi phục lỗi tốt đẹp - ví dụ: nếu chủ đề tạo tệp bị chết nặng vì một lý do nào đó, một chuỗi khác có thể đến và quyết định viết lại tệp vì thời gian tạo quá cũ. Bạn cũng có thể tạo quy trình dọn dẹp hàng loạt đơn giản và nhận dữ liệu chính xác về tần suất dữ liệu nhất định đang được sử dụng cho tệp, tần suất cập nhật thông tin (bằng cách xem thời gian tạo). Ngoài ra, bạn tránh phải thực hiện nhiều tìm kiếm đĩa trên hệ thống tệp của mình vì các luồng khác nhau tìm kiếm các tệp khác nhau khắp nơi - đặc biệt nếu bạn quyết định có nhiều máy front-end tìm kiếm trên một ổ đĩa chung.

Điều phức tạp - bạn sẽ phải đảm bảo rằng db của bạn hỗ trợ khóa cấp hàng trên bảng mà chuỗi ghi vào khi tạo tệp vì nếu không bản thân bảng có thể bị khóa, điều này có thể làm chậm điều này.

0

Câu hỏi cũ và đã có câu trả lời được đánh dấu. Tuy nhiên tôi muốn đăng một thay thế đơn giản hơn.

Tôi nghĩ rằng chúng ta có thể trực tiếp sử dụng câu lệnh khóa trên tên tập tin, như sau:

lock(string.Intern("FileLock:absoluteFilePath.txt")) 
{ 
    // your code here 
} 

Nói chung, khóa một chuỗi là một ý tưởng tồi vì chuỗi interning. Nhưng trong trường hợp đặc biệt này, nó phải đảm bảo rằng không ai khác có thể truy cập khóa đó. Chỉ cần sử dụng cùng một chuỗi khóa trước khi cố gắng đọc. Ở đây interning làm việc cho chúng ta và không chống lại.

PS: Văn bản 'FileLock' chỉ là một số văn bản tùy ý để đảm bảo rằng các đường dẫn tệp chuỗi khác không bị ảnh hưởng.

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