Dostęp do plików asynchronicznych (C#)

Do uzyskiwania dostępu do plików można użyć funkcji asynchronicznych. Za pomocą funkcji asynchronicznej można wywołać metody asynchroniczne bez używania wywołań zwrotnych ani dzielenia kodu na wiele metod lub wyrażeń lambda. Aby utworzyć asynchroniczny kod synchroniczny, wystarczy wywołać metodę asynchroniczną zamiast metody synchronicznej i dodać kilka słów kluczowych do kodu.

Możesz rozważyć następujące przyczyny dodawania asynchronii do wywołań dostępu do plików:

  • Asynchrony sprawia, że aplikacje interfejsu użytkownika są bardziej dynamiczne, ponieważ wątek interfejsu użytkownika, który uruchamia operację, może wykonać inną pracę. Jeśli wątek interfejsu użytkownika musi wykonywać kod, który zajmuje dużo czasu (na przykład więcej niż 50 milisekund), interfejs użytkownika może zostać zamrożony do momentu ukończenia operacji we/wy, a wątek interfejsu użytkownika może ponownie przetworzyć dane wejściowe klawiatury i myszy oraz inne zdarzenia.
  • Asynchronia zwiększa skalowalność ASP.NET i innych aplikacji opartych na serwerze, zmniejszając potrzebę wątków. Jeśli aplikacja używa dedykowanego wątku na odpowiedź, a tysiąc żądań jest obsługiwany jednocześnie, potrzebne są tysiące wątków. Operacje asynchroniczne często nie muszą używać wątku podczas oczekiwania. Używają one istniejącego wątku uzupełniania we/wy krótko na końcu.
  • Opóźnienie operacji dostępu do plików może być bardzo niskie w bieżących warunkach, ale opóźnienie może znacznie wzrosnąć w przyszłości. Na przykład plik może zostać przeniesiony na serwer, który znajduje się na całym świecie.
  • Dodatkowe obciążenie związane z używaniem funkcji asynchronicznych jest małe.
  • Zadania asynchroniczne można łatwo uruchamiać równolegle.

Używanie odpowiednich klas

W prostych przykładach w tym temacie przedstawiono przykłady i File.WriteAllTextAsyncFile.ReadAllTextAsync. Aby uzyskać precyzyjną kontrolę nad operacjami we/wy pliku, użyj FileStream klasy , która ma opcję powodującą asynchroniczne operacje we/wy na poziomie systemu operacyjnego. Korzystając z tej opcji, można uniknąć blokowania wątku wątek w wielu przypadkach. Aby włączyć tę opcję, należy określić useAsync=true argument or options=FileOptions.Asynchronous w wywołaniu konstruktora.

Nie można użyć tej opcji za StreamReader pomocą polecenia i StreamWriter jeśli otworzysz je bezpośrednio, określając ścieżkę pliku. Można jednak użyć tej opcji, jeśli podasz im otwartą FileStream klasęStream. Wywołania asynchroniczne są szybsze w aplikacjach interfejsu użytkownika, nawet jeśli wątek puli wątków jest zablokowany, ponieważ wątek interfejsu użytkownika nie jest blokowany podczas oczekiwania.

Pisanie tekstu

W poniższych przykładach napisz tekst do pliku. W każdej instrukcji await metoda natychmiast kończy działanie. Po zakończeniu operacji we/wy pliku metoda zostanie wznowiona w instrukcji , która jest zgodna z instrukcją await. Modyfikator asynchroniczny jest w definicji metod korzystających z instrukcji await.

Prosty przykład

public async Task SimpleWriteAsync()
{
    string filePath = "simple.txt";
    string text = $"Hello World";

    await File.WriteAllTextAsync(filePath, text);
}

Przykład kontrolki skończonej

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);
}

Oryginalny przykład zawiera instrukcję await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);, która jest skurczem następujących dwóch instrukcji:

Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
await theTask;

Pierwsza instrukcja zwraca zadanie i powoduje uruchomienie przetwarzania plików. Druga instrukcja z oczekiwaniem powoduje natychmiastowe zamknięcie metody i zwrócenie innego zadania. Po zakończeniu przetwarzania pliku wykonanie powróci do instrukcji, która następuje po oczekiwaniu.

Odczytywanie tekstu

W poniższych przykładach odczytano tekst z pliku.

Prosty przykład

public async Task SimpleReadAsync()
{
    string filePath = "simple.txt";
    string text = await File.ReadAllTextAsync(filePath);

    Console.WriteLine(text);
}

Przykład kontrolki skończonej

Tekst jest buforowany i w tym przypadku umieszczony w obiekcie StringBuilder. W przeciwieństwie do poprzedniego przykładu ocena oczekiwania generuje wartość. Metoda ReadAsync zwraca wartośćInt32<>Task , więc ocena oczekiwania generuje Int32 wartość numRead po zakończeniu operacji. Aby uzyskać więcej informacji, zobacz Async Return Types (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();
}

Równoległe asynchroniczne operacje we/wy

W poniższych przykładach pokazano przetwarzanie równoległe, zapisując 10 plików tekstowych.

Prosty przykład

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);
}

Przykład kontrolki skończonej

Dla każdego pliku WriteAsync metoda zwraca zadanie, które następnie jest dodawane do listy zadań. Instrukcja await Task.WhenAll(tasks); kończy działanie metody i wznawia działanie metody podczas przetwarzania plików dla wszystkich zadań podrzędnych.

Przykład zamyka wszystkie FileStream wystąpienia w finally bloku po zakończeniu zadań. Jeśli każda z nich FileStream została utworzona w using instrukcji, FileStream element może zostać usunięty przed ukończeniem zadania.

Każdy wzrost wydajności jest prawie całkowicie z przetwarzania równoległego, a nie z przetwarzania asynchronicznego. Zaletą asynchronii jest to, że nie jest on wiązany z wieloma wątkami i że nie jest on wiązany z wątkiem interfejsu użytkownika.

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();
        }
    }
}

W przypadku korzystania z WriteAsync metod i ReadAsync można określić element CancellationToken, którego można użyć do anulowania operacji w połowie strumienia. Aby uzyskać więcej informacji, zobacz Anulowanie w zarządzanych wątkach.

Zobacz też