Skip to main content

This recipe walks us through how to create a file, and how to read and write data asynchronously in C#.

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//
// Working with files asynchronously
//
// This recipe walks us through how to create a file, and how to read and write
// data asynchronously.
//
// How it works...
//
// When the program runs, we create four files in different manners and fill
// them up with random data. In the first case, we use the FileStream class and
// its methods, converting an Asynchronous Programming Model API to a task; in
// the second case, we do the same, but we provide FileOptions.Asynchronous to
// the FileStream constructor.
//
//     NOTE: It is very important to use the FileOptions.Asynchronous option. If
//     we omit this option, we can still work with the file in an asynchronous
//     manner, but this is just an asynchronous delegate invocation on a thread
//     pool! We use the I/O asynchrony with the FileStream class only if we
//     provide this option (or bool useAsync in another constructor overload).
//
// The third case uses some simplifying APIs such as the File.Create method and
// the StreamWriter class. It still uses I/O threads, which we are able to check
// by using the stream.IsAsync property. The last case illustrates that
// oversimplifying is also bad. Here we do not leverage the I/O asynchrony by
// imitating it with the help of asynchronous delegate invocation.
//
// Now we perform parallel asynchronous reading from files, summing their
// content, and then sum it with each other. Finally, we delete all the files.
// As there is no asynchronous delete file in any non-Windows store application,
// we simulate the asynchrony using the Task.Run factory method.
//

namespace Chapter9.Recipe1
{
    internal class Program
    {
        const int BUFFER_SIZE = 4096;

        static void Main(string[] args)
        {
            var t = ProcessAsynchronousIO();
            t.GetAwaiter().GetResult();
        }

        async static Task ProcessAsynchronousIO()
        {
            using (var stream = new FileStream("test1.txt",
                FileMode.Create,
                FileAccess.ReadWrite,
                FileShare.None, BUFFER_SIZE))
            {
                Console.WriteLine("1. Uses I/O Threads: {0}", stream.IsAsync);

                byte[] buffer = Encoding.UTF8.GetBytes(CreateFileContent());
                var writeTask = Task.Factory.FromAsync(stream.BeginWrite,
                    stream.EndWrite, buffer, 0, buffer.Length, null);

                await writeTask;
            }

            using (var stream = new FileStream("test2.txt",
                FileMode.Create,
                FileAccess.ReadWrite,
                FileShare.None, BUFFER_SIZE,
                FileOptions.Asynchronous))
            {
                Console.WriteLine("2. Uses I/O Threads: {0}", stream.IsAsync);

                byte[] buffer = Encoding.UTF8.GetBytes(CreateFileContent());
                var writeTask = Task.Factory.FromAsync(stream.BeginWrite,
                    stream.EndWrite, buffer, 0, buffer.Length, null);

                await writeTask;
            }

            using (var stream = File.Create("test3.txt", BUFFER_SIZE, FileOptions.Asynchronous))
            {
                using (var sw = new StreamWriter(stream))
                {
                    Console.WriteLine("3. Uses I/O Threads: {0}",
                        stream.IsAsync);

                    await sw.WriteAsync(CreateFileContent());
                }
            }

            using (var sw = new StreamWriter("test4.txt", true))
            {
                Console.WriteLine("4. Uses I/O Threads: {0}",
                    ((FileStream)sw.BaseStream).IsAsync);

                await sw.WriteAsync(CreateFileContent());
            }

            Console.WriteLine("Starting parsing files in parallel");

            Task<long>[] readTasks = new Task<long>[4];
            for (int i = 0; i < 4; i++)
            {
                readTasks[i] = SumFileContent(string.Format("test{0}.txt", i + 1));
            }

            long[] sums = await Task.WhenAll(readTasks);
            Console.WriteLine("Sum in all files: {0}", sums.Sum());

            Console.WriteLine("Deleting files...");
            Task[] deleteTasks = new Task[4];
            for (int i = 0; i < 4; i++)
            {
                string fileName = string.Format("test{0}.txt", i + 1);
                deleteTasks[i] = SimulateAsynchronousDelete(fileName);
            }

            await Task.WhenAll(deleteTasks);

            Console.WriteLine("Deleting complete.");
        }

        static string CreateFileContent()
        {
            var sb = new StringBuilder();

            for (int i = 0; i < 100000; i++)
            {
                sb.AppendFormat("{0}", new Random(i).Next(0, 99999));
                sb.AppendLine();
            }

            return sb.ToString();
        }

        async static Task<long> SumFileContent(string fileName)
        {
            using (var stream = new FileStream(
                fileName,
                FileMode.Open,
                FileAccess.Read,
                FileShare.None,
                BUFFER_SIZE,
                FileOptions.Asynchronous))
            {
                using (var sr = new StreamReader(stream))
                {
                    long sum = 0;

                    while (sr.Peek() > -1)
                    {
                        string line = await sr.ReadLineAsync();
                        sum += long.Parse(line);
                    }

                    return sum;
                }
            }
        }

        static Task SimulateAsynchronousDelete(string fileName)
        {
            return Task.Run(() => File.Delete(fileName));
        }
    }
}