Asynchronní přístup k souborům (C#)
Pro přístup k souborům můžete použít asynchronní funkci. Pomocí asynchronní funkce můžete volat asynchronní metody bez použití zpětných volání nebo rozdělení kódu na více metod nebo výrazů lambda. Aby byl synchronní kód asynchronní, stačí místo synchronní metody volat asynchronní metodu a přidat do kódu několik klíčových slov.
Pro přidání asynchrony do volání přístupu k souborům můžete zvážit následující důvody:
- Díky Asynchrony jsou aplikace uživatelského rozhraní lépe responzivní, protože vlákno uživatelského rozhraní, které operaci spouští, může provádět jinou práci. Pokud vlákno uživatelského rozhraní musí spouštět kód, který trvá dlouhou dobu (například více než 50 milisekund), uživatelské rozhraní může zmrznout, dokud se vstupně-výstupní operace neskončí a vlákno uživatelského rozhraní může znovu zpracovat vstup z klávesnice a myši a další události.
- Asynchrony zlepšuje škálovatelnost ASP.NET a dalších serverových aplikací tím, že snižuje potřebu vláken. Pokud aplikace používá vyhrazené vlákno na odpověď a současně se zpracovává tisíc požadavků, je potřeba tisíc vláken. Asynchronní operace často během čekání nemusí používat vlákno. Použijí existující vlákno dokončení vstupně-výstupních operací krátce na konci.
- Latence operace přístupu k souborům může být za současných podmínek velmi nízká, ale latence se může v budoucnu výrazně zvýšit. Soubor se například může přesunout na server, který je po celém světě.
- Dodatečná režie při používání funkce Async je malá.
- Asynchronní úlohy lze snadno spouštět paralelně.
Použití odpovídajících tříd
Jednoduché příklady v tomto tématu ukazují File.WriteAllTextAsync a File.ReadAllTextAsync. Pro přesnou kontrolu nad vstupně-výstupními operacemi se soubory použijte FileStream třídu , která má možnost, která způsobuje asynchronní vstupně-výstupní operace na úrovni operačního systému. Pomocí této možnosti můžete zabránit blokování vlákna fondu vláken v mnoha případech. Chcete-li povolit tuto možnost, zadejte useAsync=true
argument nebo options=FileOptions.Asynchronous
ve volání konstruktoru.
Tuto možnost nemůžete použít s StreamReader a StreamWriter , pokud je otevřete přímo zadáním cesty k souboru. Tuto možnost však můžete použít, pokud zadáte objekt, Stream který třída otevřela FileStream . Asynchronní volání jsou v aplikacích uživatelského rozhraní rychlejší i v případě, že je vlákno fondu vláken zablokované, protože vlákno uživatelského rozhraní není během čekání blokováno.
Zápis textu
Následující příklady zapisuje text do souboru. Při každém příkazu await se metoda okamžitě ukončí. Po dokončení vstupně-výstupních operací souboru metoda pokračuje v příkazu, který následuje po příkazu await. Asynchronní modifikátor je v definici metod, které používají příkaz await.
Jednoduchý příklad
public async Task SimpleWriteAsync()
{
string filePath = "simple.txt";
string text = $"Hello World";
await File.WriteAllTextAsync(filePath, text);
}
Příklad konečného řízení
public async Task ProcessWriteAsync()
{
string filePath = "temp.txt";
string text = $"Hello World{Environment.NewLine}";
await WriteTextAsync(filePath, text);
}
async Task WriteTextAsync(string filePath, string text)
{
byte[] encodedText = Encoding.Unicode.GetBytes(text);
using var sourceStream =
new FileStream(
filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true);
await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
}
Původní příklad obsahuje příkaz await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
, což je kontrakce následujících dvou příkazů:
Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
await theTask;
První příkaz vrátí úlohu a způsobí, že se spustí zpracování souboru. Druhý příkaz s operátorem await způsobí, že se metoda okamžitě ukončí a vrátí jinou úlohu. Po pozdějším dokončení zpracování souboru se spuštění vrátí k příkazu, který následuje za příkazem await.
Přečtení textu
Následující příklady čtou text ze souboru.
Jednoduchý příklad
public async Task SimpleReadAsync()
{
string filePath = "simple.txt";
string text = await File.ReadAllTextAsync(filePath);
Console.WriteLine(text);
}
Příklad konečného řízení
Text se uloží do vyrovnávací paměti a v tomto případě se umístí do StringBuilder. Na rozdíl od předchozího příkladu se při vyhodnocení funkce await vytvoří hodnota . Metoda ReadAsync vrátí hodnotuInt32<>Task , takže vyhodnocení funkce await po dokončení operace vytvoří Int32
hodnotu .numRead
Další informace najdete v tématu Asynchronní návratové typy (C#).
public async Task ProcessReadAsync()
{
try
{
string filePath = "temp.txt";
if (File.Exists(filePath) != false)
{
string text = await ReadTextAsync(filePath);
Console.WriteLine(text);
}
else
{
Console.WriteLine($"file not found: {filePath}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
async Task<string> ReadTextAsync(string filePath)
{
using var sourceStream =
new FileStream(
filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096, useAsync: true);
var sb = new StringBuilder();
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
string text = Encoding.Unicode.GetString(buffer, 0, numRead);
sb.Append(text);
}
return sb.ToString();
}
Paralelní asynchronní vstupně-výstupní operace
Následující příklady ukazují paralelní zpracování zápisem 10 textových souborů.
Jednoduchý příklad
public async Task SimpleParallelWriteAsync()
{
string folder = Directory.CreateDirectory("tempfolder").Name;
IList<Task> writeTaskList = new List<Task>();
for (int index = 11; index <= 20; ++ index)
{
string fileName = $"file-{index:00}.txt";
string filePath = $"{folder}/{fileName}";
string text = $"In file {index}{Environment.NewLine}";
writeTaskList.Add(File.WriteAllTextAsync(filePath, text));
}
await Task.WhenAll(writeTaskList);
}
Příklad konečného řízení
Pro každý soubor vrátí metoda úkol, WriteAsync který se pak přidá do seznamu úkolů. Příkaz await Task.WhenAll(tasks);
ukončí metodu a po dokončení zpracování souboru pro všechny úlohy pokračuje v metodě.
Příklad zavře všechny FileStream instance v finally
bloku po dokončení úkolů. Pokud byl každý FileStream
místo toho vytvořen v using
příkazu, FileStream
může být odstraněn před dokončením úkolu.
Jakékoli zvýšení výkonu je téměř výhradně z paralelního zpracování, nikoli asynchronního zpracování. Výhodou asynchrony je, že neváže více vláken a že nesváže vlákno uživatelského rozhraní.
public async Task ProcessMultipleWritesAsync()
{
IList<FileStream> sourceStreams = new List<FileStream>();
try
{
string folder = Directory.CreateDirectory("tempfolder").Name;
IList<Task> writeTaskList = new List<Task>();
for (int index = 1; index <= 10; ++ index)
{
string fileName = $"file-{index:00}.txt";
string filePath = $"{folder}/{fileName}";
string text = $"In file {index}{Environment.NewLine}";
byte[] encodedText = Encoding.Unicode.GetBytes(text);
var sourceStream =
new FileStream(
filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true);
Task writeTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
sourceStreams.Add(sourceStream);
writeTaskList.Add(writeTask);
}
await Task.WhenAll(writeTaskList);
}
finally
{
foreach (FileStream sourceStream in sourceStreams)
{
sourceStream.Close();
}
}
}
Při použití WriteAsync metod a ReadAsync můžete zadat CancellationToken, který můžete použít ke zrušení operace uprostřed datového proudu. Další informace najdete v tématu Zrušení ve spravovaných vláknech.