// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT License.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
namespace System.Reactive.Concurrency
{
///
/// Controls how completion or failure is handled when a or
/// is wrapped as an and observed by
/// an .
///
///
///
/// This type can be passed to overloads of the various method that adapt a TPL task as an
/// . It deals with two concerns that arise whenever this is done:
/// the scheduler through which notifications are delivered, and the handling of exceptions
/// that occur after all observers have unsubscribed.
///
///
/// If the property is non-null, it will be used to deliver all
/// notifications to observers, whether those notifications occur immediately (because the task
/// had already finished by the time it was observed) or they happen later.
///
///
/// The property determines how to deal with tasks
/// that fail after unsubscription (i.e., if an application calls
/// on an observable wrapping, then calls Dispose on the result before that task completes, and
/// the task subsequently enters a faulted state). Overloads that don't take a
/// argument do not observe the in this case, with the result that
/// the exception will then emerge from
/// (which could terminate the process, depending on how the .NET application has been
/// configured). This is consistent with how unobserved failures are
/// normally handled, but it is not consistent with how Rx handles post-unsubcription failures
/// in general. For example, if the projection callback for Select is in progress at the moment
/// an observer unsubscribes, and that callback then goes on to throw an exception, that
/// exception is simply swallowed. (One could argue that it should instead be sent to some
/// application-level unhandled exception handler, but the current behaviour has been in place
/// for well over a decade, so it's not something we can change.) So there is an argument that
/// post-unsubscribe failures in -wrapped tasks should be
/// ignored in exactly the same way: the default behaviour for post-unsubscribe failures in
/// tasks is inconsistent with the handling of all other post-unsubscribe failures. This has
/// also been the case for over a decade, so that inconsistency of defaults cannot be changed,
/// but the property enables applications to
/// ask for task-originated post-unsubscribe exceptions to be ignored in the same way as
/// non-task-originated post-unsubscribe exceptions are. (Where possible, applications should
/// avoid getting into situations where they throw exceptions in scenarios where nothing is
/// able to observe them is. This setting is a last resort for situations in which this is
/// truly unavoidable.)
///
///
public sealed class TaskObservationOptions
{
public TaskObservationOptions(
IScheduler? scheduler,
bool ignoreExceptionsAfterUnsubscribe)
{
Scheduler = scheduler;
IgnoreExceptionsAfterUnsubscribe = ignoreExceptionsAfterUnsubscribe;
}
///
/// Gets the optional scheduler to use when delivering notifications of the tasks's
/// progress.
///
///
/// If this is null, the behaviour depends on whether the task has already completed. If
/// the task has finished, the relevant completion or error notifications will be delivered
/// via . If the task is still running (or not yet
/// started) at the instant at which it is observed through Rx, no scheduler will be used
/// if this property is null.
///
public IScheduler? Scheduler { get; }
///
/// Gets a flag controlling handling of exceptions that occur after cancellation
/// has been initiated by unsubscribing from the observable representing the task's
/// progress.
///
///
/// If this is true, exceptions that occur after all observers have unsubscribed
/// will be handled and silently ignored. If false, they will go unobserved, meaning
/// they will eventually emerge through .
///
public bool IgnoreExceptionsAfterUnsubscribe { get; }
internal Value ToValue() => new(Scheduler, IgnoreExceptionsAfterUnsubscribe);
///
/// Value-type representation.
///
///
///
/// The public API surface area for is a class because
/// using a value type would run into various issues. The type might appear in expression
/// trees due to use of , which limits us
/// to a fairly old subset of C#. It means we can't use the in modifier on
/// parameters, which in turn prevents us from passing options by reference, increasing the
/// overhead of each method call. Also, options types such as this aren't normally value
/// types, so it would be a curious design choice.
///
///
/// The downside of using a class is that it entails an extra allocation. Since the feature
/// for which this is designed (the ability to swallow unhandled exceptions thrown by tasks
/// after unsubscription) is one we don't expect most applications to use, that shouldn't
/// be a problem. However, to accommodate this feature, common code paths shared by various
/// overloads need the information that a holds. The
/// easy approach would be to construct an instance of this type in overloads that don't
/// take one as an argument. But that would be impose an additional allocation on code that
/// doesn't want this new feature.
///
///
/// So although we can't use a value type with in in public APIs dues to constraints
/// on expression trees, we can do so internally. This type is a value-typed version of
/// enabling us to share code paths without forcing
/// new allocations on existing code.
///
///
internal readonly struct Value
{
internal Value(IScheduler? scheduler, bool ignoreExceptionsAfterUnsubscribe)
{
Scheduler = scheduler;
IgnoreExceptionsAfterUnsubscribe = ignoreExceptionsAfterUnsubscribe;
}
public IScheduler? Scheduler { get; }
public bool IgnoreExceptionsAfterUnsubscribe { get; }
}
}
}