Async Operator Naming Convention

Aug 17, 2013 at 6:11 PM
Hi,

I'm trying to identify a consistent naming convention for Rx operators related to Task and Task<T> (henceforth referred to plainly as task.)

I had always assumed that all task-related operators were given an Async suffix; however, upon closer examination this theory appears to be very inconsistent. But I'm also having a hard time identifying any consistent pattern.

Before providing evidence, I'd like to point out that in order to conform to the recommended guideline for naming async methods, only operators that return a task should have an Async suffix. (Unless perhaps there's a problem with overload resolution in a particular operator and Async is used to distinguish between them since it's task-related?)

It seems as though the intent was for Rx to extend the above guideline to include any operator that has a delegate parameter involving a task. Although it does so inconsistently and with several exceptions.

As of Rx 2.1.30214.0, the only task-returning operators (non-conversion) are these:

(NOTE: I've removed generic type arguments from the signatures to save horizontal space.)
Task ForEachAsync(this IObservable source, Action<T, int> onNext);
Task ForEachAsync(this IObservable source, Action<T> onNext);
Task ForEachAsync(this IObservable source, Action<T, int> onNext, CancellationToken cancellationToken);
Task ForEachAsync(this IObservable source, Action<T> onNext, CancellationToken cancellationToken);
The remaining operators in Rx with an Async suffix involve a task in a Func<> or no task at all. Some operators are not named Async yet they involve a task.

Of particular interest is Defer and DeferAsync. DeferAsync is almost identical to the task-based overload of Defer with the only difference being that DeferAsync passes in a cancellation token.

CATEGORIES

Async operators that have a Func<task> parameter:
IObservable<T> DeferAsync(Func<CancellationToken, Task<IObservable<T>>> observableFactoryAsync);

IObservable<T> FromAsync(Func<CancellationToken, Task<T>> functionAsync);
IObservable<Unit> FromAsync(Func<CancellationToken, Task> actionAsync);
IObservable<T> FromAsync(Func<Task<T>> functionAsync);
IObservable<Unit> FromAsync(Func<Task> actionAsync);

IObservable<T> StartAsync(Func<CancellationToken, Task<T>> functionAsync);
IObservable<Unit> StartAsync(Func<CancellationToken, Task> actionAsync);
IObservable<T> StartAsync(Func<Task<T>> functionAsync);
IObservable<Unit> StartAsync(Func<Task> actionAsync);
Non-Async operators that have a Func<task> parameter:
IObservable<T> Defer(Func<Task<IObservable<T>>> observableFactoryAsync);

IObservable<T> Create(Func<IObserver<T>, CancellationToken, Task<Action>> subscribeAsync);
IObservable<T> Create(Func<IObserver<T>, CancellationToken, Task<IDisposable>> subscribeAsync);
IObservable<T> Create(Func<IObserver<T>, CancellationToken, Task> subscribeAsync);
IObservable<T> Create(Func<IObserver<T>, Task<Action>> subscribeAsync);
IObservable<T> Create(Func<IObserver<T>, Task<IDisposable>> subscribeAsync);
IObservable<T> Create(Func<IObserver<T>, Task> subscribeAsync);

IObservable<T> SelectMany(this IObservable<T> source, Func<T, CancellationToken, Task<T2>> selector);
IObservable<T> SelectMany(this IObservable<T> source, Func<T, Task<T2>> selector);
IObservable<T> SelectMany(this IObservable<T> source, Func<T, CancellationToken, Task<T2>> taskSelector, Func<T, T2, T3> resultSelector);
IObservable<T> SelectMany(this IObservable<T> source, Func<T, Task<T2>> taskSelector, Func<T, T2, T3> resultSelector);

IObservable<T> Using(Func<CancellationToken, Task<T>> resourceFactoryAsync, Func<T, CancellationToken, Task<IObservable<T2>>> observableFactoryAsync);
Non-Async operators that involve a task without a Func<task> parameter:
IObservable<T> Concat(this IObservable<Task<T>> sources);

IObservable<T> Merge(this IObservable<Task<T>> sources);

IObservable<T> Switch(this IObservable<Task<T>> sources);
Async operators that do not involve tasks at all:
IObservable<T> FirstAsync(this IObservable<T> source);
IObservable<T> FirstAsync(this IObservable<T> source, Func<T, bool> predicate);

IObservable<T> FirstOrDefaultAsync(this IObservable<T> source);
IObservable<T> FirstOrDefaultAsync(this IObservable<T> source, Func<T, bool> predicate);

IObservable<T> LastAsync(this IObservable<T> source);
IObservable<T> LastAsync(this IObservable<T> source, Func<T, bool> predicate);

IObservable<T> LastOrDefaultAsync(this IObservable<T> source);
IObservable<T> LastOrDefaultAsync(this IObservable<T> source, Func<T, bool> predicate);

AsyncSubject<T> RunAsync(this IConnectableObservable<T> source, CancellationToken cancellationToken);
AsyncSubject<T> RunAsync(this IObservable<T> source, CancellationToken cancellationToken);

IObservable<T> SingleAsync(this IObservable<T> source);
IObservable<T> SingleAsync(this IObservable<T> source, Func<T, bool> predicate);
IObservable<T> SingleOrDefaultAsync(this IObservable<T> source);
IObservable<T> SingleOrDefaultAsync(this IObservable<T> source, Func<T, bool> predicate);
Are there any plans to remedy this? Or is it too much of a breaking change?

Thanks,
Dave