2010-09-27 15 views
6

Potrzebuję otworzyć plik, ale jeśli obecnie nie jest dostępny, muszę poczekać, aż będzie gotowy. Jakie jest najlepsze podejście?Jaki jest właściwy wzór do oczekiwania na blokadę pliku, który ma zostać wydany?

SCENARIUSZ

używam pliki jako trwałego mechanizmu buforowania danych aplikacji. Te dane muszą być często odczytywane i deserializowane (zapisywane tylko raz i usuwane sporadycznie). Mam proces czyszczenia, który działa w oddzielnym wątku, który określa, które pliki nie są już potrzebne i usuwa je. Otwieranie i odczytywanie plików może odbywać się jednocześnie (rzadko, ale może się zdarzyć) i chcę, aby proces ten czekał i spróbował ponownie odczytać dane.

Dzięki!

+0

Czy to wszystko odbywające się w ramach jednego procesu? –

+0

tak. W rzeczywistości jest używany na telefonie z Windows 7 (Silverlight). – Micah

Odpowiedz

0

Podobnie jak wszystkie pytania "co jest najlepszym podejściem", ten zależy od Twoich potrzeb. Niektóre opcje, które przychodzą łatwo do głowy:

  1. przerwać próbę
  2. Loop, aż plik zostanie odblokowany
  3. Poproś użytkownika, co z tym zrobić

Który z nich wybierzesz zależy możesz sobie z tym poradzić.

+1

1) nie jest bardzo skutecznym sposobem czekania, aż blokada stanie się dostępna. 3) nie jest rozwiązaniem programowym, choć być może właściwym. I 2) jest, jak podejrzewam, domyślnym rozwiązaniem, które Micah próbuje poprawić. –

+0

Chciałbym zaznaczyć, że pierwotne pytanie nie wyglądało tak jak jego obecna forma. – Tergiver

2

To zależy od tego, kto kontroluje plik. Jeśli jedna część aplikacji musi poczekać, aż inna część aplikacji zakończy przygotowywanie pliku, można użyć numeru ManualResetEvent. Oznacza to, że przy uruchomieniu, program tworzy nowy zdarzenie:

public ManualResetEvent FileEvent = new ManualResetEvent(false);

Teraz część programu, który jest oczekiwanie na plik ten kod:

FileEvent.WaitOne();

a część programu, który jest tworzenie pliku robi to, gdy plik jest gotowy:

FileEvent.Set();

Jeśli aplikacja musi czekać na plik, który jest używany przez inną aplikację, nad którą nie masz kontroli, jedynym rozwiązaniem jest nieustanne próby otwarcia pliku.

FileStream f = null; 
while (f == null) 
{ 
    try 
    { 
     f = new FileStream(...); 
    } 
    catch (IOException) 
    { 
     // wait a bit and try again 
     Thread.Sleep(5000); 
    } 
} 

Oczywiście, to prawdopodobnie nie będzie chciał złapać IOException bezwarunkowo. Prawdopodobnie chciałbyś uchwycić wyjątki, o których wiesz, jak sobie poradzić (na przykład, nie chcesz próbować ponownie, jeśli masz DirectoryNotFoundException). Funkcje We/Wy dokumentują, które wyjątki powinny być wysyłane iw jakich okolicznościach.

9

Nie jestem wielkim fanem try/catch IOException ponieważ:

  1. Powodem wyjątkiem jest nieznany.
  2. Nie lubię "oczekiwanych" wyjątków, ponieważ często uruchamiam z przerwą dotyczącą wykluczenia.

Można to zrobić bez wyjątków przez wywołanie CreateFile i powrocie strumień kiedy/jeśli w końcu zwraca uchwyt:

public static System.IO.Stream WaitForExclusiveFileAccess(string filePath, int timeout) 
{ 
    IntPtr fHandle; 
    int errorCode; 
    DateTime start = DateTime.Now; 

    while(true) 
    { 
     fHandle = CreateFile(filePath, EFileAccess.GenericRead | EFileAccess.GenericWrite, EFileShare.None, IntPtr.Zero, 
          ECreationDisposition.OpenExisting, EFileAttributes.Normal, IntPtr.Zero); 

     if (fHandle != IntPtr.Zero && fHandle.ToInt64() != -1L) 
      return new System.IO.FileStream(fHandle, System.IO.FileAccess.ReadWrite, true); 

     errorCode = Marshal.GetLastWin32Error(); 

     if (errorCode != ERROR_SHARING_VIOLATION) 
      break; 
     if (timeout >= 0 && (DateTime.Now - start).TotalMilliseconds > timeout) 
      break; 
     System.Threading.Thread.Sleep(100); 
    } 


    throw new System.IO.IOException(new System.ComponentModel.Win32Exception(errorCode).Message, errorCode); 
} 

#region Win32 
const int ERROR_SHARING_VIOLATION = 32; 

[Flags] 
enum EFileAccess : uint 
{ 
    GenericRead = 0x80000000, 
    GenericWrite = 0x40000000 
} 

[Flags] 
enum EFileShare : uint 
{ 
    None = 0x00000000, 
} 

enum ECreationDisposition : uint 
{ 
    OpenExisting = 3, 
} 

[Flags] 
enum EFileAttributes : uint 
{ 
    Normal = 0x00000080, 
} 

[DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode)] 
static extern IntPtr CreateFile(
    string lpFileName, 
    EFileAccess dwDesiredAccess, 
    EFileShare dwShareMode, 
    IntPtr lpSecurityAttributes, 
    ECreationDisposition dwCreationDisposition, 
    EFileAttributes dwFlagsAndAttributes, 
    IntPtr hTemplateFile); 

#endregion 
2

Im bardziej rodzajowy wersja metoda csharptest.net mógłby wyglądać następująco (również stosowane SafeFileHandle i rzucanie wyjątku usuwane po przekroczeniu tego limitu, można uzyskać wartości enum w http://www.pinvoke.net/default.aspx/kernel32.createfile):

public static FileStream WaitForFileAccess(string filePath, FileMode fileMode, FileAccess access, FileShare share, TimeSpan timeout) 
    { 
     int errorCode; 
     DateTime start = DateTime.Now; 

     while (true) 
     { 
      SafeFileHandle fileHandle = CreateFile(filePath, ConvertFileAccess(access), ConvertFileShare(share), IntPtr.Zero, 
                ConvertFileMode(fileMode), EFileAttributes.Normal, IntPtr.Zero); 

      if (!fileHandle.IsInvalid) 
      { 
       return new FileStream(fileHandle, access); 
      } 

      errorCode = Marshal.GetLastWin32Error(); 

      if (errorCode != ERROR_SHARING_VIOLATION) 
      { 
       break; 
      } 

      if ((DateTime.Now - start) > timeout) 
      { 
       return null; // timeout isn't an exception 
      } 

      Thread.Sleep(100); 
     } 

     throw new IOException(new Win32Exception(errorCode).Message, errorCode); 
    } 

    private static EFileAccess ConvertFileAccess(FileAccess access) 
    { 
     return access == FileAccess.ReadWrite ? EFileAccess.GenericRead | EFileAccess.GenericWrite : access == FileAccess.Read ? EFileAccess.GenericRead : EFileAccess.GenericWrite; 
    } 

    private static EFileShare ConvertFileShare(FileShare share) 
    { 
     return (EFileShare) ((uint) share); 
    } 

    private static ECreationDisposition ConvertFileMode(FileMode mode) 
    { 
     return mode == FileMode.Open ? ECreationDisposition.OpenExisting : mode == FileMode.OpenOrCreate ? ECreationDisposition.OpenAlways : (ECreationDisposition) (uint) mode; 
    } 

    [DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode)] 
    private static extern SafeFileHandle CreateFile(
     string lpFileName, 
     EFileAccess dwDesiredAccess, 
     EFileShare dwShareMode, 
     IntPtr lpSecurityAttributes, 
     ECreationDisposition dwCreationDisposition, 
     EFileAttributes dwFlagsAndAttributes, 
     IntPtr hTemplateFile); 
Powiązane problemy