Skip to content

Async creation factories

A factory builds a stream for you. The async factories are static methods on SignalAsync, plus a few extension methods that turn something you already have into an async stream.

using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Async;

The examples read a whole stream with ToListAsync, which you can read about on the results page.

Single values and empty streams

Emit

Emit sends one value, then completes.

List<int> values = await SignalAsync.Emit(42).ToListAsync();
Console.WriteLine(string.Join(", ", values));

Output:

42

Empty

Empty completes at once and sends nothing.

List<int> values = await SignalAsync.Empty<int>().ToListAsync();
Console.WriteLine(values.Count);

Output:

0

Never

Never sends nothing and never ends. It is useful in tests, and as a placeholder for "no stream yet". A terminal such as ToListAsync would wait forever, so give it a CancellationToken or a time limit.

using var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));

try
{
    await SignalAsync.Never<int>().ToListAsync(timeout.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("gave up waiting");
}

Output:

gave up waiting

Fail

Fail ends the stream at once with the exception you give.

try
{
    await SignalAsync.Fail<int>(new InvalidOperationException("no data")).ToListAsync();
}
catch (InvalidOperationException error)
{
    Console.WriteLine(error.Message);
}

Output:

no data

Sequences

Range

Range sends a run of int values, counting up from a start value, then completes.

Console.WriteLine(string.Join(", ", await SignalAsync.Range(5, 3).ToListAsync()));

Output:

5, 6, 7

FromEnumerable and FromAsyncEnumerable

FromEnumerable sends each item of a collection. FromAsyncEnumerable sends each item of an IAsyncEnumerable<T>, waiting for each one. Each subscriber reads the collection from the start.

Console.WriteLine(string.Join(", ", await SignalAsync.FromEnumerable(["a", "b"]).ToListAsync()));
Console.WriteLine(string.Join(", ", await SignalAsync.FromAsyncEnumerable(CountAsync()).ToListAsync()));

static async IAsyncEnumerable<int> CountAsync()
{
    for (var i = 1; i <= 3; i++)
    {
        await Task.Delay(10);
        yield return i;
    }
}

Output:

a, b
1, 2, 3

ToAsyncSignal

ToAsyncSignal is an extension method that does the same job from the other side. It turns an IEnumerable<T>, an IAsyncEnumerable<T> or a Task<T> into an async stream. On a Task with no result, it sends RxVoid when the task finishes.

Console.WriteLine(string.Join(", ", await new[] { 1, 2 }.ToAsyncSignal().ToListAsync()));
Console.WriteLine(string.Join(", ", await Task.FromResult("done").ToAsyncSignal().ToListAsync()));

Output:

1, 2
done

Async work

FromAsync

FromAsync runs an async method once for each subscriber, and sends its result. The method gets a CancellationToken that cancels when the subscription is disposed.

IObservableAsync<string> page = SignalAsync.FromAsync(static async cancellationToken =>
{
    await Task.Delay(50, cancellationToken);
    return "page loaded";
});

Console.WriteLine(await page.FirstAsync());

Output:

page loaded

On a Func<CancellationToken, ValueTask> with no result, the extension method FromAsync() sends RxVoid when the work finishes.

Start

Start runs a plain method and sends what it returns. Pass a TaskScheduler to choose where it runs. On an Action, the extension method Start() runs the action and sends RxVoid.

Console.WriteLine(await SignalAsync.Start(static () => 6 * 7).FirstAsync());

Output:

42

Defer

Defer calls your factory each time someone subscribes, and subscribes to the stream it returns. Each subscriber gets a fresh stream. The async overload lets the factory await before it builds the stream.

var calls = 0;
IObservableAsync<int> fresh = SignalAsync.Defer(() => SignalAsync.Emit(++calls));

Console.WriteLine(await fresh.FirstAsync());
Console.WriteLine(await fresh.FirstAsync());

Output:

1
2

The factory changes calls, so it cannot be static.

Use

Use creates a resource for each subscriber, builds a stream from it, and disposes the resource when the stream ends. The resource is an IAsyncDisposable, created by an async factory.

IObservableAsync<string> lines = SignalAsync.Use(
    static _ => ValueTask.FromResult(new Connection()),
    static connection => SignalAsync.Emit(connection.Read()));

Console.WriteLine(await lines.FirstAsync());

public sealed class Connection : IAsyncDisposable
{
    public string Read() => "hello";

    public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}

Output:

hello

Building your own stream

Create

Create builds a stream from your own subscribe method. It gets the witness to send values to and a CancellationToken, and returns an IAsyncDisposable that cleans up. A witness is the object that receives a stream's values: here an IObserverAsync<T>.

using ReactiveUI.Primitives.Async.Disposables;

IObservableAsync<int> countdown = SignalAsync.Create<int>(static async (witness, cancellationToken) =>
{
    await witness.OnNextAsync(3, cancellationToken);
    await witness.OnNextAsync(2, cancellationToken);
    await witness.OnNextAsync(1, cancellationToken);
    await witness.OnCompletedAsync(Result.Success);
    return DisposableAsync.Create(static () => ValueTask.CompletedTask);
});

Console.WriteLine(string.Join(", ", await countdown.ToListAsync()));

Output:

3, 2, 1

DisposableAsync.Create builds an IAsyncDisposable from a method. See writing your own operator.

CreateAsBackgroundJob

CreateAsBackgroundJob runs your async method as a background job for each subscriber. Send values to the witness from the job. The job's CancellationToken cancels when the subscription is disposed. Returning from the job does not end the stream, so send the completion yourself.

IObservableAsync<int> job = SignalAsync.CreateAsBackgroundJob<int>(static async (witness, cancellationToken) =>
{
    for (var i = 1; i <= 3; i++)
    {
        await Task.Delay(10, cancellationToken);
        await witness.OnNextAsync(i, cancellationToken);
    }

    await witness.OnCompletedAsync(Result.Success);
});

Console.WriteLine(string.Join(", ", await job.ToListAsync()));

Output:

1, 2, 3

Overloads take a TaskScheduler to run the job on, or startSynchronously: true to start it on the subscribing thread.

Timers

The timers send long counters. After, Timer and Interval take an optional TimeProvider, so a test can drive a fake clock. Pass FakeTimeProvider from the Microsoft.Extensions.TimeProvider.Testing package.

After

After waits, sends 0, then completes. Give it a period as well, and it keeps sending 1, 2 and so on, once per period.

Console.WriteLine(string.Join(", ", await SignalAsync.After(TimeSpan.FromMilliseconds(50)).ToListAsync()));

Console.WriteLine(string.Join(", ", await SignalAsync.After(TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(20))
                                                     .Take(3)
                                                     .ToListAsync()));

Output:

0
0, 1, 2

Every

Every sends 0, 1, 2 and so on, once per period, forever. The first value comes one period after you subscribe.

Console.WriteLine(string.Join(", ", await SignalAsync.Every(TimeSpan.FromMilliseconds(20)).Take(3).ToListAsync()));

Output:

0, 1, 2

Interval

Interval also ticks once per period, forever, with a counter from 0. Unlike Every, it takes an optional TimeProvider.

Console.WriteLine(string.Join(", ", await SignalAsync.Interval(TimeSpan.FromMilliseconds(20)).Take(3).ToListAsync()));

Output:

0, 1, 2

Many streams at once

Blend

Blend subscribes to all the streams you give at once, and sends values from each as they arrive.

Chain

Chain subscribes to the streams one after another. Each starts when the one before it completes.

IObservableAsync<int> first = SignalAsync.Range(1, 2);
IObservableAsync<int> second = SignalAsync.Range(10, 2);

Console.WriteLine(string.Join(", ", await SignalAsync.Chain(first, second).ToListAsync()));
Console.WriteLine((await SignalAsync.Blend(first, second).ToListAsync()).Count);

Output:

1, 2, 10, 11
4

The combination page covers the same operators on a stream you already hold.

RxVoid streams

RxVoid is a value that carries no data. It suits a stream that only says "something happened".

Console.WriteLine((await SignalAsyncReactiveExtensions.EmitRxVoid().ToListAsync()).Count);

Output:

1

Every factory at a glance

FactorySecond nameWhat it does
SignalAsync.EmitReturnOne value, then completes.
SignalAsync.EmptyNoneCompletes with no values.
SignalAsync.NeverNever sends and never ends.
SignalAsync.FailThrowEnds with an exception.
SignalAsync.RangeSequenceA run of int values.
SignalAsync.FromEnumerableEach item of a collection.
SignalAsync.FromAsyncEnumerableEach item of an IAsyncEnumerable<T>.
ToAsyncSignal()A collection, an IAsyncEnumerable<T> or a task as a stream.
SignalAsync.FromAsyncThe result of an async method, per subscriber.
SignalAsync.StartThe result of a plain method.
SignalAsync.DeferA fresh stream per subscriber.
SignalAsync.UseUsingA stream built from a resource that is disposed when the stream ends.
SignalAsync.CreateA stream from your own subscribe method.
SignalAsync.CreateAsBackgroundJobA stream fed by a background job.
SignalAsync.AfterTimer0 after a wait, then optionally once per period.
SignalAsync.EveryPulseA counter from 0, once per period.
SignalAsync.IntervalA counter from 0, once per period, with an optional TimeProvider.
SignalAsync.BlendAll streams at once.
SignalAsync.ChainStreams one after another.
SignalAsyncReactiveExtensions.EmitRxVoidOne RxVoid, then completes.
Action.Start()Runs an action and sends RxVoid.
Func<CancellationToken, ValueTask>.FromAsync()Runs async work and sends RxVoid.
Task.ToAsyncSignal()Sends RxVoid when the task finishes.