Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cancellation token support for asynchronous reading from a stream. #156

Merged
merged 1 commit into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
ig-sinicyn marked this conversation as resolved.
Show resolved Hide resolved
throw new OperationCanceledException(cancellationToken);
}
}

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