Skip to content
Ian Griffiths By Ian Griffiths Technical Fellow I
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 with CompositeDisposable (see video)
  • New TakeUntil overload taking a CancellationToken (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 same Observable.Throw)
  • you perform multiple awaits that cause that same exception to be thrown
The Introduction to Rx.NET (v6.1) 3rd Edition (2025) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

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.)

Programming C# 12 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

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:

Ian Griffiths

Technical Fellow I

Ian Griffiths

Ian has worked in various aspects of computing, including computer networking, embedded real-time systems, broadcast television systems, medical imaging, and all forms of cloud computing. Ian is a Technical Fellow at endjin, and 17 times Microsoft MVP in Developer Technologies. He is the author of O'Reilly's Programming C# 12.0, and has written Pluralsight courses on WPF fundamentals (WPF advanced topics WPF v4) and the TPL. He's a maintainer of Reactive Extensions for .NET, Reaqtor, and endjin's 50+ open source projects. Ian has given over 20 talks while at endjin. Technology brings him joy.