Dela via


Metodtips för att skriva till filer

viktiga API:er

Utvecklare stöter ibland på en uppsättning vanliga problem när de använder metoderna Write i FileIO- och PathIO- klasser för att utföra I/O-åtgärder för filsystem. Vanliga problem är till exempel:

  • En fil skrivs delvis.
  • Appen får ett undantag när den anropar någon av metoderna.
  • Åtgärderna lämnar kvar . TMP-filer med ett filnamn som liknar målfilens namn.

Metoderna Write för FileIO och PathIO innehåller följande:

  • WriteBufferAsync
  • WriteBytesAsync
  • WriteLinesAsync
  • WriteTextAsync

Den här artikeln innehåller information om hur dessa metoder fungerar så att utvecklare förstår bättre när och hur de ska användas. Den här artikeln innehåller riktlinjer och försöker inte tillhandahålla en lösning för alla möjliga I/O-problem för filer.

Anmärkning

 Den här artikeln fokuserar på FileIO metoder i exempel och diskussioner. Men metoderna PathIO följer ett liknande mönster och de flesta riktlinjerna i den här artikeln gäller även för dessa metoder.

Bekvämlighet kontra kontroll

Ett StorageFile--objekt är inte ett filhandtag som den interna Win32-programmeringsmodellen. I stället är en StorageFile- en representation av en fil med metoder för att ändra innehållet.

Att förstå det här konceptet är användbart när du utför I/O med en StorageFile-. Till exempel visar avsnittet Skriva till en fil tre sätt att skriva till en fil:

De två första scenarierna är de som används oftast av appar. Att skriva till filen i en enda åtgärd är enklare att koda och underhålla, och det tar också bort ansvaret för appen från att hantera många av komplexiteterna i fil-I/O. Den här bekvämligheten medför dock en kostnad: förlust av kontroll över hela åtgärden och möjligheten att fånga upp fel vid specifika tidpunkter.

Transaktionsmodellen

Metoderna Write för FileIO och PathIO-klasser omsluter stegen i den tredje skrivmodellen som beskrivs ovan med ett extra lager. Det här lagret är inkapslat i en lagringstransaktion.

För att skydda den ursprungliga filens integritet om något går fel när du skriver data använder metoderna Write en transaktionsmodell genom att öppna filen med OpenTransactedWriteAsync. Den här processen skapar ett StorageStreamTransaction- objekt. När det här transaktionsobjektet har skapats, skriver API:erna data på ett liknande sätt som File Access--exempel eller som kodexemplet i artikeln StorageStreamTransaction.

Följande diagram illustrerar de underliggande uppgifter som utförs av metoden WriteTextAsync i en lyckad skrivåtgärd. Den här bilden ger en förenklad vy över åtgärden. Den hoppar till exempel över steg som textkodning och asynkron slutförande av olika trådar.

UWP API-anropssekvensdiagram för att skriva till en fil

Fördelarna med att använda metoderna Write för FileIO och PathIO klasser i stället för den mer komplexa fyrastegsmodellen med hjälp av en dataström är:

  • Ett API-anrop för att hantera alla mellanliggande steg, inklusive fel.
  • Den ursprungliga filen behålls om något går fel.
  • Systemtillståndet försöker hållas så rent som möjligt.

Men med så många möjliga mellanliggande felpunkter finns det en ökad risk för fel. När ett fel inträffar kan det vara svårt att förstå var processen misslyckades. I följande avsnitt visas några av de fel du kan stöta på när du använder metoderna Write och tillhandahåller möjliga lösningar.

Vanliga felkoder för skrivmetoder för FileIO- och PathIO-klasserna

Den här tabellen innehåller vanliga felkoder som apputvecklare stöter på när de använder metoderna Write. Stegen i tabellen motsvarar stegen i föregående diagram.

Felnamn (värde) Steg Orsaker Lösningar
ERROR_ÅTKOMST_NEKAD (0X80070005) 5 Den ursprungliga filen kan vara markerad för borttagning, eventuellt från en tidigare åtgärd. Försök utföra åtgärden igen.
Se till att åtkomsten till filen är synkroniserad.
ERROR_SHARING_VIOLATION (0x80070020) - Delningsfeluppstod 5 Den ursprungliga filen är låst av en annan exklusiv skrivoperation. Försök utföra åtgärden igen.
Se till att åtkomsten till filen är synkroniserad.
ERROR_UNABLE_TO_REMOVE_REPLACED (0x80070497) 19-20 Det gick inte att ersätta den ursprungliga filen (file.txt) eftersom den används. En annan process eller åtgärd fick åtkomst till filen innan den kunde ersättas. Försök utföra åtgärden igen.
Se till att åtkomsten till filen är synkroniserad.
ERROR_DISK_FULL (0x80070070) - Diskutrymmet är fullt 7, 14, 16, 20 Den transaktionsmodellen skapar en extra fil, vilket kräver mer lagringsutrymme.
ERROR_OUTOFMEMORY (Otillräckligt minne) (0x8007000E) 14, 16 Detta kan inträffa på grund av flera utestående I/O-åtgärder eller stora filstorlekar. En mer detaljerad metod genom att kontrollera strömmen kan lösa felet.
E_FAIL (0x80004005) Vilken som helst Övrigt Försök att utföra åtgärden igen. Om det fortfarande misslyckas kan det vara ett plattformsfel och appen bör avslutas eftersom den är i ett inkonsekvent tillstånd.

Andra överväganden för filtillstånd som kan leda till fel

Förutom fel som returneras av metoderna Write finns här några riktlinjer för vad en app kan förvänta sig när du skriver till en fil.

Data skrevs till filen om och endast om åtgärden slutfördes

Din app bör inte göra något antagande om data i filen medan en skrivåtgärd pågår. Om du försöker komma åt filen innan en åtgärd slutförs kan det leda till inkonsekventa data. Din app bör ansvara för att spåra utestående I/Os.

Läsare

Om filen som skrivs till också används av en artig läsare (d.v.s. öppnas med FileAccessMode.Readmisslyckas efterföljande läsningar med ett fel ERROR_OPLOCK_HANDLE_CLOSED (0x80070323). Ibland försöker appar öppna filen igen när åtgärden Write pågår. Detta kan resultera i ett loppvillkor där Write slutligen misslyckas när den försöker skriva över den ursprungliga filen eftersom den inte kan ersättas.

Filer från KnownFolders

Din app kanske inte är den enda app som försöker komma åt en fil som finns på någon av de KnownFolders-. Det finns ingen garanti för att om åtgärden lyckas förblir innehållet som en app skrev till filen konstant nästa gång den försöker läsa filen. Dessutom blir delnings- eller åtkomstnekande fel vanligare i det här scenariot.

I/O i konflikt

Risken för samtidighetsfel kan minskas om vår app använder metoderna Write för filer i sina lokala data, men viss försiktighet krävs fortfarande. Om flera skrivåtgärder skickas samtidigt till filen finns det ingen garanti för vilka data som hamnar i filen. För att minimera detta rekommenderar vi att din app serialiserar Skriv åtgärder till filen.

~TMP-filer

Ibland, om åtgärden avbryts kraftfullt (till exempel om appen har pausats eller avslutats av operativsystemet), slutförs transaktionen inte eller avslutas på ett lämpligt sätt. Detta kan lämna kvar filer med ett (.~TMP)-tillägg. Överväg att ta bort dessa temporära filer (om de finns i appens lokala data) när du hanterar appaktiveringen.

Överväganden baserat på filtyper

Vissa fel kan bli vanligare beroende på typ av filer, hur ofta de används och deras filstorlek. I allmänhet finns det tre kategorier av filer som appen kan komma åt:

  • Filer som skapats och redigerats av användaren i appens lokala datamapp. Dessa skapas och redigeras endast när du använder din app, och de finns bara i appen.
  • Appmetadata. Appen använder dessa filer för att hålla reda på sitt eget tillstånd.
  • Andra filer på platser i filsystemet där din app har deklarerat funktioner för åtkomst. Dessa finns oftast i någon av de KnownFolders.

Din app har fullständig kontroll över de två första kategorierna av filer, eftersom de ingår i appens paketfiler och endast används av din app. För filer i den senaste kategorin måste din app vara medveten om att andra appar och OS-tjänster kan komma åt filerna samtidigt.

Beroende på app kan åtkomsten till filerna variera beroende på frekvens:

  • Mycket låg. Det här är vanligtvis filer som öppnas en gång när appen startas och sparas när appen pausas.
  • Låg Det här är filer som användaren specifikt vidtar en åtgärd på (till exempel spara eller läsa in).
  • Medelhög eller hög. Det här är filer där appen ständigt måste uppdatera data (till exempel autosparfunktioner eller konstant metadataspårning).

För filstorlek bör du överväga prestandadata i följande diagram för metoden WriteBytesAsync. I det här diagrammet jämförs tiden för att slutföra en åtgärd jämfört med filstorleken, jämfört med en genomsnittlig prestanda på 1 0000 åtgärder per filstorlek i en kontrollerad miljö.

WriteBytesAsync-prestanda

Tidsvärdena på y-axeln utelämnas avsiktligt från det här diagrammet eftersom olika maskinvara och konfigurationer ger olika absoluta tidsvärden. Vi har dock konsekvent observerat dessa trender i våra tester:

  • För mycket små filer (<= 1 MB): Tiden för att slutföra åtgärderna är konsekvent snabb.
  • För större filer (> 1 MB): Tiden för att slutföra åtgärderna börjar öka exponentiellt.

I/O vid applikationsavstängning

Appen måste vara utformad för att hantera avstängning om du vill behålla tillståndsinformation eller metadata för användning i senare sessioner. Bakgrundsinformation om appavstängning finns i Applivscykel och det här blogginlägget.

Om inte operativsystemet beviljar utökad körning till din app, har den 5 sekunder på sig att frigöra alla sina resurser och spara sina data när appen är avstängd. För bästa tillförlitlighet och användarupplevelse förutsätter du alltid att den tid du behöver för att hantera avstängningsuppgifter är begränsad. Tänk på följande riktlinjer under den fem sekunders tidsperioden för hantering av suspenderingsuppgifter:

  • Försök att hålla I/O till ett minimum för att undvika tävlingsförhållanden som orsakas av tömnings- och lanseringsåtgärder.
  • Undvik att skriva filer som kräver hundratals millisekunder eller mer för att skriva.
  • Om din app använder metoderna Write bör du tänka på alla mellanliggande steg som dessa metoder kräver.

Om din app använder en liten mängd tillståndsdata under avstängningen kan du i de flesta fall använda metoderna Write för att rensa data. Men om din app använder en stor mängd tillståndsdata bör du överväga att använda strömmar för att lagra dina data direkt. Detta kan bidra till att minska fördröjningen som introduceras av transaktionsmodellen för metoderna Write.

Ett exempel finns i exemplet BasicSuspension.

Andra exempel och resurser

Här är flera exempel och andra resurser för specifika scenarier.

Kodexempel för att försöka igen med fil-I/O

Följande är ett pseudokodexempel på hur du försöker skriva igen (C#), förutsatt att skrivningen ska göras när användaren har valt en fil för att spara:

Windows.Storage.Pickers.FileSavePicker savePicker = new Windows.Storage.Pickers.FileSavePicker();
savePicker.FileTypeChoices.Add("Plain Text", new List<string>() { ".txt" });
Windows.Storage.StorageFile file = await savePicker.PickSaveFileAsync();

Int32 retryAttempts = 5;

const Int32 ERROR_ACCESS_DENIED = unchecked((Int32)0x80070005);
const Int32 ERROR_SHARING_VIOLATION = unchecked((Int32)0x80070020);

if (file != null)
{
    // Application now has read/write access to the picked file.
    while (retryAttempts > 0)
    {
        try
        {
            retryAttempts--;
            await Windows.Storage.FileIO.WriteTextAsync(file, "Text to write to file");
            break;
        }
        catch (Exception ex) when ((ex.HResult == ERROR_ACCESS_DENIED) ||
                                   (ex.HResult == ERROR_SHARING_VIOLATION))
        {
            // This might be recovered by retrying, otherwise let the exception be raised.
            // The app can decide to wait before retrying.
        }
    }
}
else
{
    // The operation was cancelled in the picker dialog.
}

Synkronisera åtkomst till filen

Parallel Programming med .NET-bloggen är en bra resurs för vägledning om parallell programmering. I synnerhet beskriver -inlägget om AsyncReaderWriterLock hur du behåller exklusiv åtkomst till en fil för skrivningar samtidigt som du tillåter samtidig läsåtkomst. Tänk på att serialisering av I/O påverkar prestanda.

Se även