Skip to content

Enable async-over-sync FileStream read/writes to be cancelable on Windows - #87103

Merged
stephentoub merged 2 commits into
dotnet:mainfrom
stephentoub:filestreamcancellation
Jun 16, 2023
Merged

stephentoub merged 2 commits into
dotnet:mainfrom
stephentoub:filestreamcancellation

Conversation

@stephentoub

@stephentoub stephentoub commented Jun 4, 2023 •

Copy link
Copy Markdown
Member

Using the same helpers we previously used to enable this in pipe streams on Windows, enable FileStream.Read/WriteAsync on a FileStream created for synchronous I/O to be cancelable. This also makes some tweaks to those helpers to reduce allocation when a cancelable token is supplied.

Fixes #84290

This also makes some tweaks to those helpers to reduce allocation when a cancelable token is supplied. There's no meaningful improvement for FileStream, since it was effectively ignoring the CancellationToken previously. So here's a benchmark showing the impact on PipeStream, which as noted was already cancelable using this mechanism:

Method Toolchain Mean Ratio Allocated Alloc Ratio
ReadWriteAsync \main\corerun.exe 3.979 us 1.00 84 B 1.00
ReadWriteAsync \pr\corerun.exe 3.342 us 0.85 2 B 0.02
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.IO.Pipes;
using System.Threading.Tasks;
using System.Threading;

[MemoryDiagnoser(false)]
public class Program
{
    static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

    private readonly CancellationTokenSource _cts = new CancellationTokenSource();
    private readonly byte[] _buffer = new byte[1];
    private AnonymousPipeServerStream _server;
    private AnonymousPipeClientStream _client;

    [GlobalSetup]
    public void Setup()
    {
        _server = new AnonymousPipeServerStream(PipeDirection.Out);
        _client = new AnonymousPipeClientStream(PipeDirection.In, _server.ClientSafePipeHandle);
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _server.Dispose();
        _client.Dispose();
    }

    [Benchmark(OperationsPerInvoke = 1000)]
    public async Task ReadWriteAsync()
    {
        for (int i = 0; i < 1000; i++)
        {
            ValueTask<int> read = _client.ReadAsync(_buffer, _cts.Token);
            await _server.WriteAsync(_buffer, _cts.Token);
            await read;
        }
    }
}

@ghost

ghost commented Jun 4, 2023

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Using the same helpers we previously used to enable this in pipe streams on Windows, enable FileStream.Read/WriteAsync on a FileStream created for synchronous I/O to be cancelable. This also makes some tweaks to those helpers to reduce allocation when a cancelable token is supplied.

Fixes #84290

This also makes some tweaks to those helpers to reduce allocation when a cancelable token is supplied. There's no meaningful improvement for FileStream, since it was effectively ignoring the CancellationToken previously. So here's a benchmark showing the impact on PipeStream, which as noted was already cancelable using this mechanism:

Method Toolchain Mean Ratio Allocated Alloc Ratio
ReadWriteAsync \main\corerun.exe 3.979 us 1.00 84 B 1.00
ReadWriteAsync \pr\corerun.exe 3.342 us 0.85 2 B 0.02
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.IO.Pipes;
using System.Threading.Tasks;
using System.Threading;

[MemoryDiagnoser(false)]
public class Program
{
    static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

    private readonly CancellationTokenSource _cts = new CancellationTokenSource();
    private readonly byte[] _buffer = new byte[1];
    private AnonymousPipeServerStream _server;
    private AnonymousPipeClientStream _client;

    [GlobalSetup]
    public void Setup()
    {
        _server = new AnonymousPipeServerStream(PipeDirection.Out);
        _client = new AnonymousPipeClientStream(PipeDirection.In, _server.ClientSafePipeHandle);
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _server.Dispose();
        _client.Dispose();
    }

    [Benchmark(OperationsPerInvoke = 1000)]
    public async Task ReadWriteAsync()
    {
        for (int i = 0; i < 1000; i++)
        {
            ValueTask<int> read = _client.ReadAsync(_buffer, _cts.Token);
            await _server.WriteAsync(_buffer, _cts.Token);
            await read;
        }
    }
}
Author: stephentoub
Assignees: stephentoub
Labels:

area-System.IO

Milestone: -

…dows

Using the same helpers we previously used to enable this in pipe streams on Windows, enable FileStream.Read/WriteAsync on a FileStream created for synchronous I/O to be cancelable.  This also makes some tweaks to those helpers to reduce allocation when a cancelable token is supplied.
@stephentoub
stephentoub force-pushed the filestreamcancellation branch from 4df0da7 to d7bd8d5 Compare June 15, 2023 02:16

@adamsitnik adamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, it's great that the helper components were so easy to re-use. Thanks @stephentoub !

Comment thread src/libraries/Common/src/System/Threading/AsyncOverSyncWithIoCancellation.cs Outdated
@adamsitnik adamsitnik added this to the 8.0.0 milestone Jun 16, 2023
…ancellation.cs

Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@stephentoub
stephentoub merged commit e154624 into dotnet:main Jun 16, 2023
@stephentoub
stephentoub deleted the filestreamcancellation branch June 16, 2023 22:08
@ghost ghost locked as resolved and limited conversation to collaborators Jul 17, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow cancellation of Process standard IO stream operations on Windows

3 participants