在同一订阅期间更改可观察的源

时间:2018-01-10 16:18:45

标签: c# .net reactive-programming system.reactive

如何在保持订阅完整的同时更改Observable来源?是的,使用Subject很容易实现,但最佳做法是不使用主题

这是一个例子:

Worker.cs

public class Worker
{
    public IObservable<Worker> IsWorking {get;}
    public string Name {get;}

    public Worker(string name)
    {
        Name = name;
        IsWorking = Observable.Interval(TimeSpan.FromSeconds(1))
            .Select(_ => this);
    }
}

此类公开了Observable每秒触发一次。

WorkerState.cs

public class WorkerState
{
    public Worker CurrentWorker
    {
        set => WorkerIsBusy = value.IsWorking;
    }

    public IObservable<Worker> WorkerIsBusy { get; private set; }
}

此类应该包含一个工作单元,可以动态更改,但Subscription应自动附加到当前工作程序。 (这将是Subject

的地方

在这里测试:

Program.cs的

public class Program
{
    public async Task Run()
    {
        var state = new WorkerState();
        state.CurrentWorker = new Worker("A");
        state.WorkerIsBusy.Subscribe(worker =>
            Console.WriteLine($"Worker {worker.Name}"));

        await Task.Delay(3000);

        Console.WriteLine("Should change now to B...");
        state.CurrentWorker = new Worker("B");

        Console.Read();
    }
}

但我的输出如下:

Worker A
Worker A
Should change now to B...
Worker A
Worker A
Worker A
...

而不是切换到工人B.

2 个答案:

答案 0 :(得分:2)

您几乎总是用来重新订阅的方法是使用.Switch()运算符让您重新订阅查询。

这样的事情:

var switcher = new Subject<IObservable<int>>();

var subscription = switcher.Switch().Subscribe(x => Console.WriteLine(x));

switcher.OnNext(Observable.Return(42));
switcher.OnNext(Observable.Range(0, 3));

产生:

42
0
1
2

无需重新订阅。

对于您的代码,您需要将WorkerState更改为:

public class WorkerState
{
    private Worker currentWorker = null;
    private Subject<Worker> currentWorkerSubject = new Subject<Worker>();
    public Worker CurrentWorker
    {
        set
        {
            currentWorker = value;
            currentWorkerSubject.OnNext(value);
        }
    }

    public IObservable<Worker> WorkerIsBusy
    {
        get =>
            currentWorkerSubject
                .StartWith(currentWorker)
                .Where(w => w != null)
                .Select(w => w.IsWorking)
                .Switch();
    }
}

然后您的代码有效(稍微警告await Task.Delay(3000);Observable.Interval(TimeSpan.FromSeconds(1))Worker的竞争条件较小。将await更改为3500避免它。

答案 1 :(得分:0)

您遇到的问题是您已订阅了之前的WorkerIsBusy值,虽然您已更改了引用,但您尚未更改订阅,该订阅仍指向之前的值。

您需要拦截(实际订阅)worker.IsWorking在您的WorkerState类中,并在属性值更改时取消订阅/重新订阅。

或者你可以使用主题 - 我不明白为什么你不会?