2014-05-07 20 views
14

Zapisywanie konstruktora String do pliku asynchronicznie. Ten kod przejmuje kontrolę nad plikiem, zapisuje do niego strumień i uwalnia go. Obsługuje żądania z asynchronicznych operacji, które mogą wejść w dowolnym momencie.Zapisywanie do pliku w sposób bezpieczny dla wątku

Wartość FilePath jest ustawiana dla instancji klasy (w związku z czym obiekt blokady jest dla każdej instancji), ale istnieje możliwość konfliktu, ponieważ klasy te mogą współużytkować ścieżki FilePath. Taki rodzaj konfliktu, jak również wszystkie inne typy spoza instancji klasy, zostaną rozwiązane z ponownymi próbami.

Czy ten kod jest odpowiedni do tego celu? Czy istnieje lepszy sposób radzenia sobie z tym, co oznacza mniej (lub nie) poleganie na mechanizmie catch and retry?

Również, jak uniknąć wyjątków, które wystąpiły z innych przyczyn.

public string Filepath { get; set; } 
private Object locker = new Object(); 

public async Task WriteToFile(StringBuilder text) 
    { 
     int timeOut = 100; 
     Stopwatch stopwatch = new Stopwatch(); 
     stopwatch.Start(); 
     while (true) 
     { 
      try 
      { 
       //Wait for resource to be free 
       lock (locker) 
       { 
        using (FileStream file = new FileStream(Filepath, FileMode.Append, FileAccess.Write, FileShare.Read)) 
        using (StreamWriter writer = new StreamWriter(file, Encoding.Unicode)) 
        { 
         writer.Write(text.ToString()); 
        } 
       } 
       break; 
      } 
      catch 
      { 
       //File not available, conflict with other class instances or application 
      } 
      if (stopwatch.ElapsedMilliseconds > timeOut) 
      { 
       //Give up. 
       break; 
      } 
      //Wait and Retry 
      await Task.Delay(5); 
     } 
     stopwatch.Stop(); 
    } 
+0

Rzeczywiste zapisy plików nie są asynchroniczne. Miałeś na myśli, że oni są? –

+0

@StephenCleary, tak, początkowo, ale nie powinieneś czekać na rzeczy w zamku, więc musiałem to zmienić. Nie jestem pewien, w jakim stopniu to wszystko podkopuje. –

+0

To brzmi jak powinno być na codereview tbh –

Odpowiedz

27

Jak podejść do tego będzie zależeć dużo o tym, jak często piszesz. Jeśli piszesz stosunkowo niewielką ilość tekstu dość rzadko, po prostu użyj blokady statycznej i zrób to. To może być twój najlepszy zakład, ponieważ dysk może spełnić tylko jedno żądanie naraz. Zakładając, że wszystkie pliki wyjściowe znajdują się na tym samym dysku (być może nie jest to rzetelne założenie, ale należy do mnie), nie ma dużej różnicy między blokowaniem na poziomie aplikacji a blokadą, która jest wykonywana na poziomie systemu operacyjnego.

Więc jeśli deklarują locker jak:

static object locker = new object(); 

Będziesz mieć pewność, że nie ma żadnych konfliktów z innych wątków w programie.

Jeśli chcesz, aby to urządzenie było kuloodporne (lub przynajmniej w rozsądny sposób), nie możesz uniknąć wyjątków. Złe rzeczy mogą się zdarzyć. Musisz musi obsługiwać wyjątki w pewien sposób. To, co robisz w obliczu błędu, jest czymś zupełnie innym. Prawdopodobnie będziesz chciał powtórzyć kilka razy, jeśli plik jest zablokowany. Jeśli otrzymasz złą ścieżkę lub błąd nazwy pliku lub dysk pełny lub którykolwiek z wielu innych błędów, prawdopodobnie chcesz zabić program. Ponownie, to zależy od ciebie. Ale nie można uniknąć obsługi wyjątku, chyba że masz problem z awarią programu w przypadku błędu.

Nawiasem mówiąc, można wymienić cały ten kod:

   using (FileStream file = new FileStream(Filepath, FileMode.Append, FileAccess.Write, FileShare.Read)) 
       using (StreamWriter writer = new StreamWriter(file, Encoding.Unicode)) 
       { 
        writer.Write(text.ToString()); 
       } 

Z jednego połączenia:

File.AppendAllText(Filepath, text.ToString()); 

Zakładając, że używasz .NET 4.0 lub nowszy. Zobacz File.AppendAllText.

Innym sposobem, w jaki można sobie z tym poradzić, jest przypisanie wątkom wiadomości do kolejki i utworzenie dedykowanego wątku, który obsługuje kolejkowanie. Będziesz mieć BlockingCollection komunikatów i powiązanych ścieżek plików.Na przykład:

class LogMessage 
{ 
    public string Filepath { get; set; } 
    public string Text { get; set; } 
} 

BlockingCollection<LogMessage> _logMessages = new BlockingCollection<LogMessage>(); 

Twoje wątków zapisywać dane do tej kolejki:

_logMessages.Add(new LogMessage("foo.log", "this is a test")); 

uruchomieniu długo uruchomione zadania w tle, że nic nie robi, ale usługi, które kolejki:

foreach (var msg in _logMessages.GetConsumingEnumerable()) 
{ 
    // of course you'll want your exception handling in here 
    File.AppendAllText(msg.Filepath, msg.Text); 
} 

Twój potencjał ryzyko polega na tym, że wątki tworzą wiadomości zbyt szybko, co powoduje, że kolejka rośnie bez ograniczeń, ponieważ konsument nie może nadążyć. Niezależnie od tego, czy jest to prawdziwe ryzyko w Twojej aplikacji, możesz to tylko powiedzieć. Jeśli uważasz, że to może być ryzyko, możesz umieścić w kolejce maksymalny rozmiar (liczbę wpisów), aby rozmiar kolejki przekroczył tę wartość, zanim producenci będą mogli poczekać, aż w kolejce będzie miejsce w kolejce.

+0

Dzięki, bardzo przez. częściowo z tego powodu, że podoba mi się mój duży blok do używania jest to, że mogę ustawić FileShare.Read, co nie jest pewne w przypadku File.AppendAllText. Ale znowu przypuszczam, że to tylko kwestia, czy konkuruję z innymi procesami. –

11

Można również użyć ReaderWriterLock, to jest uważany za bardziej „właściwy” sposób kontroli bezpieczeństwa wątku gdy ma do czynienia z operacji odczytu i zapisu ...

Aby debugować moje aplikacje internetowe (gdy zdalne debugowanie nie) używam następne ("debug.txt" kończy się w folderze \ bin na serwerze):

public static class LoggingExtensions 
{ 
    static ReaderWriterLock locker = new ReaderWriterLock(); 
    public static void WriteDebug(string text) 
    { 
     try 
     { 
      locker.AcquireWriterLock(int.MaxValue); 
      System.IO.File.AppendAllLines(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Replace("file:\\", ""), "debug.txt"), new[] { text }); 
     } 
     finally 
     { 
      locker.ReleaseWriterLock(); 
     } 
    } 
} 

Mam nadzieję, że to zaoszczędzi ci trochę czasu.

+0

Właśnie przyszedł ten post i pomógł mi, dzięki @Matas! Pomyślałem, że poinformuję jednak, że następca ReaderWriterLock, nazwany ReaderWriterLockSlim, jest dostępny pod adresem https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim(vsv.110).aspx – KBoek

+0

ReaderWriterLock (i ReaderWriterLockSlim) są dla sytuacji, w których musisz pozwolić wielu czytelnikom i jednego pisarza. W takim przypadku, jeśli piszesz do pliku z wielu wątków, ale nie czytasz, proponuję po prostu zablokować. – Jamie

Powiązane problemy