Friday, September 14, 2018

Breaking things up onto separate threads in modern C# makes for some asynchronous messaging.

Consider this static utility, tucking away some things to static state so that they may be later used in an action.

using System;
using System.Collections.Generic;
using MyOwnStuff.Core.Objects;
namespace MyOwnStuff.Core.Utilities
{
   public static class ChunkText
   {
      private static InitialDetails _initialDetails;
      private static ExternalDependencies _externalDependencies;
      private static Action<Alert> _alertAction;
      private static Action<string> _updateFileSize;
      
      public static Alert TextOnly(InitialDetails initialDetails, ExternalDependencies
            externalDependencies, Action<Alert> alertAction, Action<string> updateFileSize)
      {
         _initialDetails = initialDetails;
         _externalDependencies = externalDependencies;
         _alertAction = alertAction;
         _updateFileSize = updateFileSize;
         Action<List<string>> writeAction = WriteLines;

         externalDependencies.FileReading.ReadLineByLine(initialDetails, writeAction);
         return new Alert(String.Format("Count slowly in your head to... sixty? Attempting
               parsing: {0}", initialDetails.FromPath), false);
      }
      
      private static void WriteLines(List<string> lines)
      {
         _updateFileSize(String.Format("{0} lines", lines.Count));
         _alertAction(new Alert(String.Format("Done parsing: {0}", _initialDetails.FromPath),
               false));
         _externalDependencies.FileWriting.WriteChunksFromLines(lines, _initialDetails,
               _alertAction);
      }
   }
}

 
 

Alright the IFileReading looks like so. Do note, no hint of async here.

using System;
using System.Collections.Generic;
using MyOwnStuff.Core.Objects;
namespace MyOwnStuff.Core.Interfaces
{
   public interface IFileReading
   {
      void ReadLineByLine(InitialDetails initialDetails, Action<List<string>> pseudoReturn);
      Byte[] SimpleRead(InitialDetails initialDetails);
   }
}

 
 

FileReading implements IFileReading and now we do have an async method even though that is not at the interface. This method needs to be void instead of returning a task as I learned the hard way that returning a task screws up the await implemenation partway down the method and sabotages the action that chases it.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using MyOwnStuff.Core.Interfaces;
using MyOwnStuff.Core.Objects;
namespace MyOwnStuff.Infrastructure.ExternalDependencies
{
   public class FileReading : IFileReading
   {
      public Byte[] SimpleRead(InitialDetails initialDetails)
      {
         return File.ReadAllBytes(initialDetails.FromPath);
      }
      
      public async void ReadLineByLine(InitialDetails initialDetails, Action<List<string>>
            pseudoReturn)
      {
         List<string> list = new List<string>() {};
         await Task.Run(() => { ReadLineByLine(list, initialDetails.FromPath); });
         pseudoReturn(list);
      }
      
      private void ReadLineByLine(List<string> list, string fromPath)
      {
         using (var streamReader = new StreamReader(fromPath))
         {
            while (streamReader.Peek() != -1)
            {
               string line = streamReader.ReadLine();
               list.Add(line);
            }
         }
      }
   }
}

 
 

We want the extra work of using async/await here to break file writing of chunks of the bigger file that would happen downstream of the read to happen on a different thread, right? Also some of the actions that we use to work around the fact that we cannot return anything may implement methods in the UI that set copy at labels to give us some reporting on what is happening in a long process. Note the use of...

streamReader.Peek() != -1

 
 

...which is pretty cool!

No comments:

Post a Comment