Skip to content

Commit

Permalink
Added cancelation token support for the operation of asynchronously r…
Browse files Browse the repository at this point in the history
…eading a string from a stream. (#156)
  • Loading branch information
TheSquidCombatant authored Jan 3, 2024
1 parent 3423ba8 commit a349a54
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 0 deletions.
101 changes: 101 additions & 0 deletions CodeJam.Main.Tests/IO/StreamTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#if NET45_OR_GREATER || TARGETS_NETSTANDARD || TARGETS_NETCOREAPP // PUBLIC_API_CHANGES

using NUnit.Framework;

using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CodeJam.IO
{
[TestFixture(Category = "Assertions")]
[SuppressMessage("ReSharper", "NotResolvedInText")]
public class StreamTests
{
[Test]
public void TestStreamReadingCanceled()
{
using var file = new FileStreamSlowWrapper(1, 3, 0);
var input = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString());

file.Write(input, 0, input.Length);
file.Flush();

var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;

file.Position = 0;
var task = file.ReadAsStringAsync(Encoding.UTF8, cancellationToken);

cancellationTokenSource.Cancel();
Assert.ThrowsAsync<OperationCanceledException>(() => task);
}

internal class FileStreamSlowWrapper : Stream
{
private FileStream fileStream;

private TimeSpan readDelay;

private TimeSpan writeDelay;

public FileStreamSlowWrapper(
int bufferSize = 4096,
double readDelaySeconds = 0,
double writeDelaySeconds = 0)
{
readDelay = TimeSpan.FromSeconds(readDelaySeconds);
writeDelay = TimeSpan.FromSeconds(writeDelaySeconds);

var filePath = System.IO.Path.Combine(
System.IO.Path.GetTempPath(),
Guid.NewGuid() + ".tmp");
fileStream = new FileStream(
filePath,
FileMode.CreateNew,
FileAccess.ReadWrite,
FileShare.Read,
bufferSize,
FileOptions.DeleteOnClose);
}

public override bool CanRead => fileStream.CanRead;

public override bool CanSeek => fileStream.CanSeek;

public override bool CanWrite => fileStream.CanWrite;

public override long Length => fileStream.Length;

public override long Position { get => fileStream.Position; set => fileStream.Position = value; }

public override void Flush() => fileStream.Flush();

public override long Seek(long offset, SeekOrigin origin) => fileStream.Seek(offset, origin);

public override void SetLength(long value) => fileStream.SetLength(value);

public override void Close() => fileStream.Close();

protected override void Dispose(bool disposing) => fileStream.Dispose();

public override int Read(byte[] buffer, int offset, int count)
{
Task.Delay(readDelay).Wait();
return fileStream.Read(buffer, offset, count);
}

public override void Write(byte[] buffer, int offset, int count)
{
Task.Delay(writeDelay).Wait();
fileStream.Write(buffer, offset, count);
}
}
}
}

#endif
27 changes: 27 additions & 0 deletions CodeJam.Main/IO/StreamExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using JetBrains.Annotations;
Expand Down Expand Up @@ -110,6 +111,32 @@ public static async Task<string> ReadAsStringAsync(
return await reader.ReadToEndAsync().ConfigureAwait(false);
}

/// <summary>
/// Returns content of the stream as a string.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <param name="encoding">The character encoding to use.</param>
/// <param name="cancellationToken">Token of cancellation of the operation.</param>
public static async Task<string> ReadAsStringAsync(
this Stream stream,
Encoding? encoding = null,
CancellationToken cancellationToken = default)
{
Code.NotNull(stream, nameof(stream));
using var register = cancellationToken.Register(() => stream.Close());

try
{
using var reader = stream.ToStreamReader(encoding, true);
return await reader.ReadToEndAsync().ConfigureAwait(false);
}
catch
{
if (!cancellationToken.IsCancellationRequested) throw;
throw new OperationCanceledException(cancellationToken);
}
}

/// <summary>
/// Returns content of the stream as a byte array.
/// </summary>
Expand Down

0 comments on commit a349a54

Please sign in to comment.