0
\$\begingroup\$

Need a ThrottledStream for a project, created the following class (still not tested), wouldn't mind some opinions on the code and suggestions on how it can be improved:

public class ThrottledStream : Stream
{
    private const int OneSecond = 1000; // 1000 milliseconds
    private readonly Stream _baseStream;
    private static readonly Stopwatch Stopwatch = new();

    public ThrottledStream(Stream stream) => _baseStream = stream;

    public int MaxBytesPerSecond { get; set; }

    public override bool CanRead => _baseStream.CanRead;

    public override bool CanSeek => _baseStream.CanSeek;

    public override bool CanWrite => _baseStream.CanWrite;

    public override long Length => _baseStream.Length;

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

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

    public override int Read(byte[] buffer, int offset, int count)
    {
        var readBytes = 0;

        while (readBytes != count)
        {
            var newOffset = readBytes + offset;
            var bytesToRead = count - readBytes > MaxBytesPerSecond ? MaxBytesPerSecond : count - readBytes;

            var throttledReadBytes = Throttle(() => _baseStream.Read(buffer, newOffset, bytesToRead));

            if (throttledReadBytes == 0) // Reached end of stream
                return readBytes;

            readBytes += throttledReadBytes;
        }

        return readBytes;
    }

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

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

    public override void Write(byte[] buffer, int offset, int count)
    {
        var writtenBytes = 0;

        while (writtenBytes != count)
        {
            var newOffset = writtenBytes + offset;
            var bytesToWrite = count - writtenBytes > MaxBytesPerSecond ? MaxBytesPerSecond : count - writtenBytes;

            Throttle(() =>
            {
                _baseStream.Write(buffer, newOffset, bytesToWrite);
                return true;
            });

            writtenBytes += bytesToWrite;
        }
    }

    private static T Throttle<T>(Func<T> func)
    {
        var start = GetElapsedMilliseconds();
        var result = func.Invoke();
        var finish = GetElapsedMilliseconds();

        var timeToSleep = OneSecond - (finish - start);

        if (timeToSleep > 0)
            Thread.Sleep((int) timeToSleep);

        return result;
    }
    
    private static long GetElapsedMilliseconds()
    {
        if (!Stopwatch.IsRunning)
            Stopwatch.Start();

        return Stopwatch.ElapsedMilliseconds;
    }
}
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Using Thread.Sleep is inefficient. I would suggest Task.Delay. Accordingly, the code should be asynchronous. \$\endgroup\$ Jul 29, 2021 at 11:43
  • 1
    \$\begingroup\$ Look at the Channels. \$\endgroup\$ Jul 29, 2021 at 11:44
  • \$\begingroup\$ @AlexanderPetrov can't use Task.Delay exactly because the code has to be synchronous: I inherit Stream which only contains abstract Read/Write/Seek methods ... \$\endgroup\$
    – cogumel0
    Jul 29, 2021 at 12:58
  • \$\begingroup\$ @AlexanderPetrov also how do you think think that Channels would improve this? I don't need to buffer anything \$\endgroup\$
    – cogumel0
    Jul 29, 2021 at 12:59

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.