Rx.NET v6.1 Now Available

We've just released a new version of the Reactive Extensions for .NET (Rx.NET). System.Reactive
v6.1.0 is now available on NuGet.
What's new?
We have three new features (hence the bump in the minor version number):
DisposeWith
extension method for use withCompositeDisposable
(see video)- New
TakeUntil
overload taking aCancellationToken
(see video) - New
ResetExceptionDispatchState
operator (see video)
DisposeWith
extension method
Thanks to Chris Pulman for implementing this.
This simplifies the use of CompositeDisposable
when using a 'fluent' coding style. E.g.:
CompositeDisposable d = new();
someObservable1.Subscribe(myObserver1)
.DisposeWith(d);
someObservable2.Subscribe(myObserver2)
.DisposeWith(d);
This video provides more detail:
TakeUntil(CancellationToken)
Thanks to Nils Aufschläger for the initial suggestion, and for implementing the operator. Thanks to Daniel Weber for proposing the design we ultimately chose.
The problem we wanted to solve was to be able to take an 'infinite' IObservable<T>
(one that never calls OnComplete
) and cause it to complete. Nils wanted to be able to trigger this completion by calling Dispose
on some object. Daniel pointed out that if we added an overload to TakeUntil
that accepts a CancellationToken
, we can use Rx's existing CancellationDisposable
to achieve this, while also enabling any other cancellation source to be used as well.
This video provides more detail:
ResetExceptionDispatchInfo
Thanks to Adam Jones for the initial issue report, and for reviewing our work on this.
Adam reported a peculiar behaviour in which Rx was causing the StackTrace
reported by an exception to grow longer each time it rethrew that exception. This would happen if the following were both true:
- something delivers the same exception to
OnError
more than once (e.g., because of multiple subscriptions to the sameObservable.Throw
) - you perform multiple
await
s that cause that same exception to be thrown
Here's a contrived example that illustrates the issue.
IObservable<int> ox = Observable.Throw<int>(new Exception("Bang!"));
try
{
await ox;
}
catch (Exception x)
{
// Exception will look normal here.
Console.WriteLine(x);
}
try
{
await ox;
}
catch (Exception x)
{
// Exception will have duplicated stack trace here.
Console.WriteLine(x);
}
Despite how it looks, this is not actually a bug. .NET itself will do exactly the same thing if you use await Task.FromException(ex)
twice on the same exception object. The basic issue here is that there are some rules around when it is acceptable to rethrow an exception.
If we were to modify either Throw
or Rx's await
integration to stop this from happening, it would break some other important scenarios in which currently we flow exception origin information correctly. That means that the code shown above will always exhibit this behaviour and we can't change that without causing new problems.
Instead, in this release of Rx, we've done two things to address this issue:
- The documentation at IntroToRx.com now alerts readers to this problem and describes how to avoid it in the various places where it is a relevant factor
- We've added a new
ResetExceptionDispatchState
operator that enables you to get the behaviour you might have expected.
We can avoid this stack trace repetition by using Rx 6.1's new ResetExceptionDispatchState
operator. All it takes is a change to the first statement:
IObservable<int> ox = Observable
.Throw<int>(new Exception("Bang!"))
.ResetExceptionDispatchState();
This tells Rx that the exception won't be populated with state such as a correct stack trace or fault bucket information at the point where it emerges from Throw
, and so we need Rx to reset that information. (In effect, this operator performs a throw
at the instant the exception emerges from the source.)
Existing scenarios that were relying on Rx's behaviour of preserving exception dispatch state info will continue to work because we have not changed the core behaviour. But examples that require that state to be reset on each call to OnError
now have a straightforward way to achieve that.
This video provides more detail:
What's next?
Now that Rx 6.1 is out, we are turning our attention to the next release. It will need to be a new major version (Rx v7.0) because it will make these breaking changes:
- We will remove
net6.0
support - UI-framework-specific functionality will no longer be available through
System.Reactive
's compile-time public API (but will remain in runtime assemblies for binary backwards compatibility); this functionality will move into new NuGet packages
These are expressed as negatives because major version bumps are always about breaking changes. So what's the upside?
When .NET 10 ships, we will of course be supporting that. But if we don't manage to get Rx v7.0 out of the door shortly after .NET 10 ships, we'll just add .NET 10 tests to the test suite for Rx 6.1. So it's not yet clear whether upgrading to v7.0 will be required for us to offer .NET 10 support. (That said, Rx 6.0 and 6.1 will in fact work just fine on .NET 10.0. It's just a question of whether we officially support that. As far as I know, Rx 4.4 also works on .NET 9 today, but that's not a combination we support.)
The reason for the UI framework packaging changes in Rx 7.0 is that it will solve a problem that happens today if you build .NET apps with AoT or self-contained deployment that target Windows. With Rx 6.0 and 6.1, if your application has a Windows-specific TFM targetting Windows 10.0.19041 or later (e.g. net6.0-windows10.0.19041
or net9.0-windows10.0.22621
), the System.Reactive
library imposes dependencies on WPF and Windows Forms. So even if you're not using either of those frameworks, an AoT deployment or a self-contained deployment will include a copy of both of those frameworks. This makes the deployable code tens of megabytes larger than it needs to be.
Unsurprisingly, this has proven unpopular. The AvaloniaUI project abandoned Rx.NET completely because of it. So we will be fixing it in Rx 7.0. We have gone to extreme lengths to minimize the impact on existing code, but there will be some breaking changes in some situations, which is why we will be bumping the major version number.
Please try it out
This new 6.1 release of System.Reactive
is available on NuGet today. If you're using Rx in your application, please try upgrading. If you have any problems, please file issues at https://github.com/dotnet/reactive/issues. Meanwhile, we hope you enjoy this new version of the Reactive Extensions for .NET.
More Rx content
As well as the two series from Carmel Eve's Rx Operators Deep Dive and Richard Kerslake's Event stream manipulation for Rx with semantic logging, you can find further information here:
- Intro to Rx.NET 3rd Edition (2025)
- Rx playlist (on the endjin YouTube channel)
- Rx 101 Workshop
- Rx talk for the dotnetsheff user group
- https://reaqtive.net/ — a persistent, reliable, distributed stream processing system based on Rx