Skip to content

Commit

Permalink
Breaking changes for FileStream (#24060)
Browse files Browse the repository at this point in the history
  • Loading branch information
gewarren authored Jul 21, 2021
1 parent 55d44fb commit 7afceb2
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/core/compatibility/6.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ If you're migrating an app to .NET 6, the breaking changes listed here might aff
| [API obsoletions with non-default diagnostic IDs](core-libraries/6.0/obsolete-apis-with-custom-diagnostics.md) | Preview 1 |
| [Changes to nullable reference type annotations](core-libraries/6.0/nullable-ref-type-annotation-changes.md) | Preview 1-2 |
| [Environment.ProcessorCount behavior on Windows](core-libraries/6.0/environment-processorcount-on-windows.md) | Preview 2 |
| [FileStream no longer synchronizes file offset with OS](core-libraries/6.0/filestream-doesnt-sync-offset-with-os.md) | Preview 4 |
| [FileStream.Position updates after ReadAsync or WriteAsync completes](core-libraries/6.0/filestream-position-updates-after-readasync-writeasync-completion.md) | Preview 4 |
| [New diagnostic IDs for obsoleted APIs](core-libraries/6.0/diagnostic-id-change-for-obsoletions.md) | Preview 5 |
| [New System.Linq.Queryable method overloads](core-libraries/6.0/additional-linq-queryable-method-overloads.md) | Preview 3-4 |
| [Older framework versions dropped from package](core-libraries/6.0/older-framework-versions-dropped.md) | Preview 5 |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
title: ".NET 6 breaking change: FileStream doesn't synchronize file offset with OS"
description: Learn about the .NET 6 breaking change in core .NET libraries where FileStream doesn't synchronize the file offset with the operating system.
ms.date: 05/05/2021
---
# FileStream no longer synchronizes file offset with OS

To improve performance, <xref:System.IO.FileStream> no longer synchronizes the file offset with the operating system.

## Change description

In previous .NET versions, <xref:System.IO.FileStream> synchronizes the file offset with the Windows operating system (OS) when it reads or writes to a file. It synchronizes the offset by calling [SetFilePointer](/windows/win32/api/fileapi/nf-fileapi-setfilepointer), which is an expensive system call. Starting in .NET 6, <xref:System.IO.FileStream> no longer synchronizes the file offset, and instead just keeps the offset in memory. <xref:System.IO.FileStream.Position?displayProperty=nameWithType> always returns the current offset, but if you obtain the file handle from <xref:System.IO.FileStream.SafeFileHandle?displayProperty=nameWithType> and query the OS for the current file offset using a system call, the offset value will be 0.

The following code shows how the file offset differs between previous .NET versions and .NET 6.

```csharp
[DllImport("kernel32.dll")]
private static extern bool SetFilePointerEx(SafeFileHandle hFile, long liDistanceToMove, out long lpNewFilePointer, uint dwMoveMethod);

byte[] bytes = new byte[10_000];
string path = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());

using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, bufferSize: 4096, useAsync: true))
{
SafeFileHandle handle = fs.SafeFileHandle;

await fs.WriteAsync(bytes, 0, bytes.Length);
Console.WriteLine(fs.Position); // 10000 in all versions
if (SetFilePointerEx(handle, 0, out long currentOffset, 1 /* get current offset */))
{
Console.WriteLine(currentOffset); // 10000 in .NET 5, 0 in .NET 6
}
}
```

## Version introduced

6.0 Preview 4

## Reason for change

This change was introduced to improve the performance of asynchronous reads and writes and to address the following issues:

- [Win32 FileStream will issue a seek on every ReadAsync call](https://github.com/dotnet/runtime/issue/16354)
- [FileStream.Windows useAsync WriteAsync calls blocking APIs](https://github.com/dotnet/runtime/issue/25905)

With this change, <xref:System.IO.FileStream.ReadAsync%2A> operations are up to two times faster, and <xref:System.IO.FileStream.WriteAsync%2A> operations are up to five times faster.

## Recommended action

- Modify any code that relied on the offset being synchronized.

- To enable the .NET 5 behavior in .NET 6, specify an `AppContext` switch or an environment variable. By setting the switch to `true`, you opt out of all performance improvements made to `FileStream` in .NET 6.

```xml
{
"configProperties": {
"System.IO.UseNet5CompatFileStream": true
}
}
```

```cmd
set DOTNET_SYSTEM_IO_USENET5COMPATFILESTREAM=1
```

## Affected APIs

None.

## See also

- [SetFilePointer function](/windows/win32/api/fileapi/nf-fileapi-setfilepointer)
- [SetFilePointerEx function](/windows/win32/api/fileapi/nf-fileapi-setfilepointerex)

<!--
### Category
- Core .NET libraries
### Affected APIS
Not detectible via API analysis.
-->
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
title: ".NET 6 breaking change: FileStream.Position updated after ReadAsync or WriteAsync completion"
description: Learn about the .NET 6 breaking change in core .NET libraries where FileStream.Position is updated after ReadAsync or WriteAsync completion.
ms.date: 05/05/2021
---
# FileStream.Position updates after ReadAsync or WriteAsync completes

<xref:System.IO.FileStream.Position?displayProperty=nameWithType> is now updated after <xref:System.IO.FileStream.ReadAsync%2A> or <xref:System.IO.FileStream.WriteAsync%2A> completes.

## Change description

In previous .NET versions on Windows, <xref:System.IO.FileStream.Position?displayProperty=nameWithType> is updated after the asynchronous read or write operation starts. Starting in .NET 6, <xref:System.IO.FileStream.Position?displayProperty=nameWithType> is updated after those operations complete.

The following code shows how the value of <xref:System.IO.FileStream.Position?displayProperty=nameWithType> differs between previous .NET versions and .NET 6.

```csharp
byte[] bytes = new byte[10_000];
string path = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());

using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, bufferSize: 4096, useAsync: true))
{
Task[] writes = new Task[3];

writes[0] = fs.WriteAsync(bytes, 0, bytes.Length);
Console.WriteLine(fs.Position); // 10000 in .NET 5, 0 in .NET 6
writes[1] = fs.WriteAsync(bytes, 0, bytes.Length);
Console.WriteLine(fs.Position); // 20000 in .NET 5, 0 in .NET 6
writes[2] = fs.WriteAsync(bytes, 0, bytes.Length);
Console.WriteLine(fs.Position); // 30000 in .NET 5, 0 in .NET 6
await Task.WhenAll(writes);
Console.WriteLine(fs.Position); // 30000 in all versions
}
```

## Version introduced

6.0 Preview 4

## Reason for change

<xref:System.IO.FileStream> has never been thread-safe, but until .NET 6, .NET has tried to support multiple concurrent calls to its asynchronous methods (<xref:System.IO.FileStream.ReadAsync%2A> and <xref:System.IO.FileStream.WriteAsync%2A>) on Windows.

This change was introduced to allow for 100% asynchronous file I/O with <xref:System.IO.FileStream> and to fix the following issues:

- [FileStream.FlushAsync ends up doing synchronous writes](https://github.com/dotnet/runtime/issue/27643)
- [Win32 FileStream turns async reads into sync reads](https://github.com/dotnet/runtime/issue/16341)

Now, when buffering is enabled (that is, the `bufferSize` argument that's passed to the [FileStream constructor](xref:System.IO.FileStream.%23ctor%2A) is greater than 1), every <xref:System.IO.FileStream.ReadAsync%2A> and <xref:System.IO.FileStream.WriteAsync%2A> operation is serialized.

## Recommended action

- Modify any code that relied on the position being set before operations completed.

- To enable the .NET 5 behavior in .NET 6, specify an `AppContext` switch or an environment variable. By setting the switch to `true`, you opt out of all performance improvements made to `FileStream` in .NET 6.

```xml
{
"configProperties": {
"System.IO.UseNet5CompatFileStream": true
}
}
```

```cmd
set DOTNET_SYSTEM_IO_USENET5COMPATFILESTREAM=1
```

## Affected APIs

- <xref:System.IO.FileStream.Position?displayProperty=fullName>

<!--
### Category
- Core .NET libraries
### Affected APIs
- `P:System.IO.FileStream.Position`
-->
8 changes: 8 additions & 0 deletions docs/core/compatibility/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ items:
href: core-libraries/6.0/obsolete-apis-with-custom-diagnostics.md
- name: Environment.ProcessorCount behavior on Windows
href: core-libraries/6.0/environment-processorcount-on-windows.md
- name: FileStream no longer synchronizes offset with OS
href: core-libraries/6.0/filestream-doesnt-sync-offset-with-os.md
- name: FileStream.Position updated after completion
href: core-libraries/6.0/filestream-position-updates-after-readasync-writeasync-completion.md
- name: New diagnostic IDs for obsoleted APIs
href: core-libraries/6.0/diagnostic-id-change-for-obsoletions.md
- name: New Queryable method overloads
Expand Down Expand Up @@ -533,6 +537,10 @@ items:
href: core-libraries/6.0/obsolete-apis-with-custom-diagnostics.md
- name: Environment.ProcessorCount behavior on Windows
href: core-libraries/6.0/environment-processorcount-on-windows.md
- name: FileStream no longer synchronizes offset with OS
href: core-libraries/6.0/filestream-doesnt-sync-offset-with-os.md
- name: FileStream.Position updated after completion
href: core-libraries/6.0/filestream-position-updates-after-readasync-writeasync-completion.md
- name: New diagnostic IDs for obsoleted APIs
href: core-libraries/6.0/diagnostic-id-change-for-obsoletions.md
- name: New Queryable method overloads
Expand Down

0 comments on commit 7afceb2

Please sign in to comment.