-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactoring Reading Text Files Part I
- Loading branch information
1 parent
ce35974
commit cdc2d5d
Showing
6 changed files
with
292 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace AehnlichLib.Files | ||
{ | ||
/// <summary>Provides a pump that supports running asynchronous methods on the current thread. | ||
/// https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c | ||
/// </summary> | ||
public static class AsyncPump | ||
{ | ||
/// <summary>Runs the specified asynchronous method.</summary> | ||
/// <param name="asyncMethod">The asynchronous method to execute.</param> | ||
public static void Run(Action asyncMethod) | ||
{ | ||
if (asyncMethod == null) throw new ArgumentNullException("asyncMethod"); | ||
|
||
var prevCtx = SynchronizationContext.Current; | ||
try | ||
{ | ||
// Establish the new context | ||
var syncCtx = new SingleThreadSynchronizationContext(true); | ||
SynchronizationContext.SetSynchronizationContext(syncCtx); | ||
|
||
// Invoke the function | ||
syncCtx.OperationStarted(); | ||
asyncMethod(); | ||
syncCtx.OperationCompleted(); | ||
|
||
// Pump continuations and propagate any exceptions | ||
syncCtx.RunOnCurrentThread(); | ||
} | ||
finally { SynchronizationContext.SetSynchronizationContext(prevCtx); } | ||
} | ||
|
||
/// <summary>Runs the specified asynchronous method.</summary> | ||
/// <param name="asyncMethod">The asynchronous method to execute.</param> | ||
public static void Run(Func<Task> asyncMethod) | ||
{ | ||
if (asyncMethod == null) throw new ArgumentNullException("asyncMethod"); | ||
|
||
var prevCtx = SynchronizationContext.Current; | ||
try | ||
{ | ||
// Establish the new context | ||
var syncCtx = new SingleThreadSynchronizationContext(false); | ||
SynchronizationContext.SetSynchronizationContext(syncCtx); | ||
|
||
// Invoke the function and alert the context to when it completes | ||
var t = asyncMethod(); | ||
if (t == null) throw new InvalidOperationException("No task provided."); | ||
t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default); | ||
|
||
// Pump continuations and propagate any exceptions | ||
syncCtx.RunOnCurrentThread(); | ||
t.GetAwaiter().GetResult(); | ||
} | ||
finally { SynchronizationContext.SetSynchronizationContext(prevCtx); } | ||
} | ||
|
||
/// <summary>Runs the specified asynchronous method.</summary> | ||
/// <param name="asyncMethod">The asynchronous method to execute.</param> | ||
public static T Run<T>(Func<Task<T>> asyncMethod) | ||
{ | ||
if (asyncMethod == null) throw new ArgumentNullException("asyncMethod"); | ||
|
||
var prevCtx = SynchronizationContext.Current; | ||
try | ||
{ | ||
// Establish the new context | ||
var syncCtx = new SingleThreadSynchronizationContext(false); | ||
SynchronizationContext.SetSynchronizationContext(syncCtx); | ||
|
||
// Invoke the function and alert the context to when it completes | ||
var t = asyncMethod(); | ||
if (t == null) throw new InvalidOperationException("No task provided."); | ||
t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default); | ||
|
||
// Pump continuations and propagate any exceptions | ||
syncCtx.RunOnCurrentThread(); | ||
return t.GetAwaiter().GetResult(); | ||
} | ||
finally { SynchronizationContext.SetSynchronizationContext(prevCtx); } | ||
} | ||
|
||
/// <summary>Provides a SynchronizationContext that's single-threaded.</summary> | ||
private sealed class SingleThreadSynchronizationContext : SynchronizationContext | ||
{ | ||
/// <summary>The queue of work items.</summary> | ||
private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue = | ||
new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); | ||
/// <summary>The processing thread.</summary> | ||
private readonly Thread m_thread = Thread.CurrentThread; | ||
/// <summary>The number of outstanding operations.</summary> | ||
private int m_operationCount = 0; | ||
/// <summary>Whether to track operations m_operationCount.</summary> | ||
private readonly bool m_trackOperations; | ||
|
||
/// <summary>Initializes the context.</summary> | ||
/// <param name="trackOperations">Whether to track operation count.</param> | ||
internal SingleThreadSynchronizationContext(bool trackOperations) | ||
{ | ||
m_trackOperations = trackOperations; | ||
} | ||
|
||
/// <summary>Dispatches an asynchronous message to the synchronization context.</summary> | ||
/// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param> | ||
/// <param name="state">The object passed to the delegate.</param> | ||
public override void Post(SendOrPostCallback d, object state) | ||
{ | ||
if (d == null) throw new ArgumentNullException("d"); | ||
m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); | ||
} | ||
|
||
/// <summary>Not supported.</summary> | ||
public override void Send(SendOrPostCallback d, object state) | ||
{ | ||
throw new NotSupportedException("Synchronously sending is not supported."); | ||
} | ||
|
||
/// <summary>Runs an loop to process all queued work items.</summary> | ||
public void RunOnCurrentThread() | ||
{ | ||
foreach (var workItem in m_queue.GetConsumingEnumerable()) | ||
workItem.Key(workItem.Value); | ||
} | ||
|
||
/// <summary>Notifies the context that no more work will arrive.</summary> | ||
public void Complete() { m_queue.CompleteAdding(); } | ||
|
||
/// <summary>Invoked when an async operation is started.</summary> | ||
public override void OperationStarted() | ||
{ | ||
if (m_trackOperations) | ||
Interlocked.Increment(ref m_operationCount); | ||
} | ||
|
||
/// <summary>Invoked when an async operation is completed.</summary> | ||
public override void OperationCompleted() | ||
{ | ||
if (m_trackOperations && | ||
Interlocked.Decrement(ref m_operationCount) == 0) | ||
Complete(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
namespace AehnlichLib.Files | ||
{ | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
/// <summary> | ||
/// File Utility Class | ||
/// https://stackoverflow.com/questions/13167934/how-to-async-files-readalllines-and-await-for-results | ||
/// </summary> | ||
public static class FileEx | ||
{ | ||
/// <summary> | ||
/// This is the same default buffer size as | ||
/// <see cref="StreamReader"/> and <see cref="FileStream"/>. | ||
/// </summary> | ||
private const int DefaultBufferSize = 4096; | ||
|
||
/// <summary> | ||
/// Indicates that | ||
/// 1. The file is to be used for asynchronous reading. | ||
/// 2. The file is to be accessed sequentially from beginning to end. | ||
/// </summary> | ||
private const FileOptions DefaultOptions = FileOptions.Asynchronous | FileOptions.SequentialScan; | ||
|
||
public static async Task<List<string>> GetFileTextLinesAsync(string path) | ||
{ | ||
var lines = new List<string>(); | ||
|
||
// Open the FileStream with the same FileMode, FileAccess | ||
// and FileShare as a call to File.OpenText would've done. | ||
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, DefaultOptions)) | ||
{ | ||
var bom = new byte[4]; // Decode bom (if any) and continue to read text content | ||
await stream.ReadAsync(bom, 0, 4); | ||
stream.Seek(0, SeekOrigin.Begin); | ||
Encoding encoding = GetEncoding(bom); | ||
|
||
using (var reader = new StreamReader(stream, encoding)) | ||
{ | ||
string line; | ||
while ((line = await reader.ReadLineAsync()) != null) | ||
{ | ||
lines.Add(line); | ||
} | ||
} | ||
} | ||
|
||
return lines; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the encoding of a file from its first 4 bytes. | ||
/// </summary> | ||
/// <param name="bom">BOM to be translated into an <see cref="Encoding"/>. | ||
/// This should be at least 4 bytes long.</param> | ||
/// <returns>Recommended <see cref="Encoding"/> to be used to read text from this file.</returns> | ||
public static Encoding GetEncoding(byte[] bom) | ||
{ | ||
// Analyze the BOM | ||
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) | ||
return Encoding.UTF7; | ||
|
||
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) | ||
return Encoding.UTF8; | ||
|
||
if (bom[0] == 0xff && bom[1] == 0xfe) | ||
return Encoding.Unicode; //UTF-16LE | ||
|
||
if (bom[0] == 0xfe && bom[1] == 0xff) | ||
return Encoding.BigEndianUnicode; //UTF-16BE | ||
|
||
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) | ||
return Encoding.UTF32; | ||
|
||
return Encoding.Default; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.