|
| 1 | +// Licensed to the .NET Foundation under one or more agreements. |
| 2 | +// The .NET Foundation licenses this file to you under the MIT License. |
| 3 | +// See the LICENSE file in the project root for more information. |
| 4 | + |
| 5 | +using System.Threading.Tasks; |
| 6 | + |
| 7 | +namespace System.Reactive.Concurrency |
| 8 | +{ |
| 9 | + /// <summary> |
| 10 | + /// Controls how completion or failure is handled when a <see cref="Task"/> or |
| 11 | + /// <see cref="Task{TResult}"/> is wrapped as an <see cref="IObservable{T}"/> and observed by |
| 12 | + /// an <see cref="IObserver{T}"/>. |
| 13 | + /// </summary> |
| 14 | + /// <remarks> |
| 15 | + /// <para> |
| 16 | + /// This type can be passed to overloads of the various method that adapt a TPL task as an |
| 17 | + /// <see cref="IObservable{T}"/>. It deals with two concerns that arise whenever this is done: |
| 18 | + /// the scheduler through which notifications are delivered, and the handling of exceptions |
| 19 | + /// that occur after all observers have unsubscribed. |
| 20 | + /// </para> |
| 21 | + /// <para> |
| 22 | + /// If the <see cref="Scheduler"/> property is non-null, it will be used to deliver all |
| 23 | + /// notifications to observers, whether those notifications occur immediately (because the task |
| 24 | + /// had already finished by the time it was observed) or they happen later. |
| 25 | + /// </para> |
| 26 | + /// <para> |
| 27 | + /// The <see cref="IgnoreExceptionsAfterUnsubscribe"/> property determines how to deal with tasks |
| 28 | + /// that fail after unsubscription (i.e., if an application calls <see cref="IObservable{T}.Subscribe(IObserver{T})"/> |
| 29 | + /// on an observable wrapping, then calls Dispose on the result before that task completes, and |
| 30 | + /// the task subsequently enters a faulted state). Overloads that don't take a <see cref="TaskObservationOptions"/> |
| 31 | + /// argument do not observe the <see cref="Task.Exception"/> in this case, with the result that |
| 32 | + /// the exception will then emerge from <see cref="TaskScheduler.UnobservedTaskException"/> |
| 33 | + /// (which could terminate the process, depending on how the .NET application has been |
| 34 | + /// configured). This is consistent with how unobserved <see cref="Task"/> failures are |
| 35 | + /// normally handled, but it is not consistent with how Rx handles post-unsubcription failures |
| 36 | + /// in general. For example, if the projection callback for Select is in progress at the moment |
| 37 | + /// an observer unsubscribes, and that callback then goes on to throw an exception, that |
| 38 | + /// exception is simply swallowed. (One could argue that it should instead be sent to some |
| 39 | + /// application-level unhandled exception handler, but the current behaviour has been in place |
| 40 | + /// for well over a decade, so it's not something we can change.) So there is an argument that |
| 41 | + /// post-unsubscribe failures in <see cref="IObservable{T}"/>-wrapped tasks should be |
| 42 | + /// ignored in exactly the same way: the default behaviour for post-unsubscribe failures in |
| 43 | + /// tasks is inconsistent with the handling of all other post-unsubscribe failures. This has |
| 44 | + /// also been the case for over a decade, so that inconsistency of defaults cannot be changed, |
| 45 | + /// but the <see cref="IgnoreExceptionsAfterUnsubscribe"/> property enables applications to |
| 46 | + /// ask for task-originated post-unsubscribe exceptions to be ignored in the same way as |
| 47 | + /// non-task-originated post-unsubscribe exceptions are. (Where possible, applications should |
| 48 | + /// avoid getting into situations where they throw exceptions in scenarios where nothing is |
| 49 | + /// able to observe them is. This setting is a last resort for situations in which this is |
| 50 | + /// truly unavoidable.) |
| 51 | + /// </para> |
| 52 | + /// </remarks> |
| 53 | + public sealed class TaskObservationOptions |
| 54 | + { |
| 55 | + public TaskObservationOptions( |
| 56 | + IScheduler? scheduler, |
| 57 | + bool ignoreExceptionsAfterUnsubscribe) |
| 58 | + { |
| 59 | + Scheduler = scheduler; |
| 60 | + IgnoreExceptionsAfterUnsubscribe = ignoreExceptionsAfterUnsubscribe; |
| 61 | + } |
| 62 | + |
| 63 | + /// <summary> |
| 64 | + /// Gets the optional scheduler to use when delivering notifications of the tasks's |
| 65 | + /// progress. |
| 66 | + /// </summary> |
| 67 | + /// <remarks> |
| 68 | + /// If this is null, the behaviour depends on whether the task has already completed. If |
| 69 | + /// the task has finished, the relevant completion or error notifications will be delivered |
| 70 | + /// via <see cref="ImmediateScheduler.Instance"/>. If the task is still running (or not yet |
| 71 | + /// started) at the instant at which it is observed through Rx, no scheduler will be used |
| 72 | + /// if this property is null. |
| 73 | + /// </remarks> |
| 74 | + public IScheduler? Scheduler { get; } |
| 75 | + |
| 76 | + /// <summary> |
| 77 | + /// Gets a flag controlling handling of exceptions that occur after cancellation |
| 78 | + /// has been initiated by unsubscribing from the observable representing the task's |
| 79 | + /// progress. |
| 80 | + /// </summary> |
| 81 | + /// <remarks> |
| 82 | + /// If this is <c>true</c>, exceptions that occur after all observers have unsubscribed |
| 83 | + /// will be handled and silently ignored. If <c>false</c>, they will go unobserved, meaning |
| 84 | + /// they will eventually emerge through <see cref="TaskScheduler.UnobservedTaskException"/>. |
| 85 | + /// </remarks> |
| 86 | + public bool IgnoreExceptionsAfterUnsubscribe { get; } |
| 87 | + |
| 88 | + internal Value ToValue() => new Value(this.Scheduler, this.IgnoreExceptionsAfterUnsubscribe); |
| 89 | + |
| 90 | + /// <summary> |
| 91 | + /// Value-type representation. |
| 92 | + /// </summary> |
| 93 | + /// <remarks> |
| 94 | + /// <para> |
| 95 | + /// The public API surface area for <see cref="TaskObservationOptions"/> is a class because |
| 96 | + /// using a value type would run into various issues. The type might appear in expression |
| 97 | + /// trees due to use of <see cref="System.Reactive.Linq.IQbservable{T}"/>, which limits us |
| 98 | + /// to a fairly old subset of C#. It means we can't use the <c>in</c> modifier on |
| 99 | + /// parameters, which in turn prevents us from passing options by reference, increasing the |
| 100 | + /// overhead of each method call. Also, options types such as this aren't normally value |
| 101 | + /// types, so it would be a curious design choice. |
| 102 | + /// </para> |
| 103 | + /// <para> |
| 104 | + /// The downside of using a class is that it entails an extra allocation. Since the feature |
| 105 | + /// for which this is designed (the ability to swallow unhandled exceptions thrown by tasks |
| 106 | + /// after unsubscription) is one we don't expect most applications to use, that shouldn't |
| 107 | + /// be a problem. However, to accommodate this feature, common code paths shared by various |
| 108 | + /// overloads need the information that a <see cref="TaskObservationOptions"/> holds. The |
| 109 | + /// easy approach would be to construct an instance of this type in overloads that don't |
| 110 | + /// take one as an argument. But that would be impose an additional allocation on code that |
| 111 | + /// doesn't want this new feature. |
| 112 | + /// </para> |
| 113 | + /// <para> |
| 114 | + /// So although we can't use a value type with <c>in</c> in public APIs dues to constraints |
| 115 | + /// on expression trees, we can do so internally. This type is a value-typed version of |
| 116 | + /// <see cref="TaskObservationOptions"/> enabling us to share code paths without forcing |
| 117 | + /// new allocations on existing code. |
| 118 | + /// </para> |
| 119 | + /// </remarks> |
| 120 | + internal readonly struct Value |
| 121 | + { |
| 122 | + internal Value(IScheduler? scheduler, bool ignoreExceptionsAfterUnsubscribe) |
| 123 | + { |
| 124 | + Scheduler = scheduler; |
| 125 | + IgnoreExceptionsAfterUnsubscribe = ignoreExceptionsAfterUnsubscribe; |
| 126 | + } |
| 127 | + |
| 128 | + public IScheduler? Scheduler { get; } |
| 129 | + public bool IgnoreExceptionsAfterUnsubscribe { get; } |
| 130 | + } |
| 131 | + } |
| 132 | +} |
0 commit comments