如何等待完成IObserver调用,包括观察用户呼叫?

时间:2016-06-23 10:02:39

标签: c# .net multithreading system.reactive

如何在调用onNext并且订阅者在另一个线程/任务上时等待订阅者方法完成?我想念等待onNext(),例如对于主题......

例如,当在同一个线程上运行时:

    [Test, Explicit]
    public void TestSyncOnSameTask()
    {
        Console.WriteLine("Starting on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
        using (var subject = new Subject<int>())
        using (subject
            .Subscribe(
                o => Console.WriteLine("Received {1} on threadId:{0}",
                    Thread.CurrentThread.ManagedThreadId,
                    o),
                () => Console.WriteLine("OnCompleted on threadId:{0}",
                    Thread.CurrentThread.ManagedThreadId)))
        {
            subject.OnNext(3);
            subject.OnNext(2);
            subject.OnNext(1);
            subject.OnCompleted();
        }
    }

将打印输出:

Starting on threadId:10
Received 3 on threadId:10
Received 2 on threadId:10
Received 1 on threadId:10
OnCompleted on threadId:10

在单独的线程上运行时(例如,卸载UI-Thread)

    [Test, Explicit]
    public void TestObserveOnSeparateTask()
    {
        Console.WriteLine("Starting on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
        using (var subject = new Subject<int>())
        using (subject.ObserveOn(TaskPoolScheduler.Default)
            .Subscribe(
                o => { Console.WriteLine("Received {1} on threadId:{0}", Thread.CurrentThread.ManagedThreadId, o);
                         Task.Delay(1000 * o);
                },
                () => Console.WriteLine("OnCompleted on threadId:{0}",
                    Thread.CurrentThread.ManagedThreadId)))
        {
            subject.OnNext(3);
            subject.OnNext(2);
            subject.OnNext(1);
            subject.OnCompleted();
        }
    }

作为输出的以下结果将是apear:

Starting on threadId:10
Received 3 on threadId:11

正如您所看到的那样......使用了左侧因为主题调用OnNext()立即返回,并且不等待订阅者任务的完成。

如何&#34;等待&#34;用户呼叫?订单有保证吗?我没有找到有关Synchronize()方法的好文档以及此方法实际同步的内容。

我寻找类似的东西:

await subject.OnNext(3);
await subject.OnNext(2);

以老派的方式&#34;我使用了BlockingCollection作为队列和

    public async Task<IResponse> Request(IRequest request)
    {
        var taskCompletionSource = new TaskCompletionSource<IResponse>();
        Task<IResponse> tsk = taskCompletionSource.Task;
        Action<IResponse> responseHandler = taskCompletionSource.SetResult; 
        this.requestProcessor.Process(request, responseHandler);
        return await tsk.ConfigureAwait(false);
    }

,请求处理器类似于:

        while (true)
        {
            //blockingCollection is shared between caller and runner
            blockingCollection.TryTake(out request, Timeout.Infinite);
            // call callback when finished to return to awaited method
        }

现在我想使用RX / Observables,但是这个卸载主题让我感到震惊......更多的是关于Reactive Extensions的发布/订阅,但我认为我需要这样的请求/响应。有什么办法用RX吗?

2 个答案:

答案 0 :(得分:3)

为什么要阻止? 我猜你是这样做的,因为你认为你需要,或者说你正在测试,并希望阻止直到测试结束以验证结果。

我将首先解决测试理论。

在Rx中,有调度程序的概念(IScheduler接口)。 您可以使用它们来控制应该在何时何地执行工作。 例如。您可以指定可以在TaskPool / ThreadPool,专用线程或调度程序(WPF)上安排工作。 您还可以指定是否应尽快完成工作,或将来某个时间。

这些类型是控制时间/并发性的首选方式,因此可以从代码中删除Task.Delay(1000 * o);

要以Rx idomatic方式复制您提供的测试,您可以使用TestScheduler和支持ITestObserver<T>ITestObservable<T>类型重写它(没有主题,不要让我开始)。

// Define other methods and classes here
public void TestObserveOnSeparateTask()
{
    var scheduler = new TestScheduler();
    var source = scheduler.CreateColdObservable<int>(
        ReactiveTest.OnNext(TimeSpan.FromSeconds(1).Ticks, 3),
        ReactiveTest.OnNext(TimeSpan.FromSeconds(2).Ticks, 2),
        ReactiveTest.OnNext(TimeSpan.FromSeconds(3).Ticks, 1),
        ReactiveTest.OnCompleted<int>(TimeSpan.FromSeconds(4).Ticks)
        );

    var observer = scheduler.CreateObserver<int>();
    var subscription = source
        .ObserveOn(scheduler)
//      .Subscribe(
//          o =>
//          {
//              Console.WriteLine("Received {1} on threadId:{0}", Thread.CurrentThread.ManagedThreadId, o);
//              //Task.Delay(1000 * o);
//          },
//          () => Console.WriteLine("OnCompleted on threadId:{0}",
//              Thread.CurrentThread.ManagedThreadId));
        .Subscribe(observer);

    scheduler.Start();

    //Pretty silly test, as we apply no query/projection to the source.
    //  Note we add 1 tick due to it being a relative/cold observable and the ObserveOn scheduling will take one time slice to perform.
    ReactiveAssert.AreElementsEqual(
        new[] {
            ReactiveTest.OnNext(TimeSpan.FromSeconds(1).Ticks + 1, 3),
            ReactiveTest.OnNext(TimeSpan.FromSeconds(2).Ticks + 1, 2),
            ReactiveTest.OnNext(TimeSpan.FromSeconds(3).Ticks + 1, 1),
            ReactiveTest.OnCompleted<int>(TimeSpan.FromSeconds(4).Ticks + 1)
        },
        observer.Messages);
}

好的,但是如果你因为想要等待某种结果,或者你想知道源已经完成而阻塞了怎么办? 在这些情况下,您希望拥抱Rx以及可以应用的连续模式。 对于SelectManyConcat之类的运算符,您可以获得一些序列,只有在其他序列生成一个值或者可能已完成之后才会触发。 我将这些模式称为异步门。 你可以看到Matt Barrett在this video在51:03解释它们。 他展示了如何在完全异步(Rx)系统中如何仅在发生其他前提条件事件时执行某些活动。 我不认为我可以在不完全知道你想要实现什么的情况下给出一个更好的例子,但我怀疑答案是阻止,如果问题涉及“UI线程”。

关于Synchronize()方法,这纯粹是为了实现不遵循序列化合同的IObservable<T>序列。 您可以应用该运算符来强制执行该合同。 理论上不应该被使用,如果每个人都遵守规则。

**编辑讨论CQRS / ES **

关于您的CQRS部分问题,如果减少耦合,也可以这样做。我想你希望发出一个命令有点请求/响应。即,您想知道该命令是否已被系统接受并成功处理。这是一个问题。另一个问题是异步更新读取模型。 在这种情况下,我可能只使用Task / Task<T>作为接受Command的方法的返回值。 独立地,读取模型将订阅由发布到聚合根的命令导致的事件的可观察序列表示。

作为一个超级人为的例子,我将DDD ES的最小实现集中在一起,我可以用它来显示命令聚合存储库 / EventStore 并观察命令产生的事件。

void Main()
{
    var repository = new EventStore();
    repository.Events
        .OfType<FooEvent>()
        .Subscribe(evt=>UpdateReadModel(evt));

    var aggreate = new MyAggregate(Guid.NewGuid());
    var command = new FooCommand(1);
    aggreate.Handle(command);
    repository.Save(aggreate);

}
public void UpdateReadModel(FooEvent fooEvent)
{
    Console.WriteLine(fooEvent.SomeValue);
}

// Define other methods and classes here
public class FooCommand
{
    public FooCommand(int someValue)
    {
        SomeValue = someValue;
    }
    public int SomeValue { get; private set; }
}
public class FooEvent
{
    public FooEvent(int someValue)
    {
        SomeValue = someValue;
    }
    public int SomeValue { get; private set; }
}
public interface IAggregate
{
    List<object> GetUncommittedEvents();
    void Commit();
}
public class MyAggregate : IAggregate
{
    private readonly Guid _id;
    private readonly List<object> _uncommittedEvents = new List<object>();
    public MyAggregate(Guid id)
    {
        _id = id;
    }

    public List<Object> GetUncommittedEvents()
    {
        return _uncommittedEvents;
    }
    public void Commit()
    {
        _uncommittedEvents.Clear();
    }

    public void Handle(FooCommand command)
    {
        //As a result of processing the FooCommand we emit the FooEvent
        var fooEvent = new FooEvent(command.SomeValue);
        _uncommittedEvents.Add(fooEvent);
    }
}

public class EventStore
{
    private readonly ISubject<object> _events = new ReplaySubject<object>();

    public IObservable<object> Events { get { return _events.AsObservable(); } }
    public void Save(IAggregate aggregate)
    {
        foreach (var evt in aggregate.GetUncommittedEvents())
        {
            _events.OnNext(evt);
        }
        aggregate.Commit();
    }
}

现在在现实世界中,您将使用GetEventStore.com,NEventStore或Cedar.EventStore之类的东西,但结构保持不变。 您在聚合处推送命令,它会生成事件,这些事件将保存到事件源,然后事件源将这些事件(带外/异步)发送到读取模型。

答案 1 :(得分:2)

为了得到你想要的东西,你可以这样做:

Console.WriteLine("Starting on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
var are = new AutoResetEvent(false);
using (var subject = new Subject<int>())
{
    using (
        subject
            .ObserveOn(TaskPoolScheduler.Default)
            .Subscribe(o =>
            {
                Console.WriteLine("Received {1} on threadId:{0}", Thread.CurrentThread.ManagedThreadId, o);
                Task.Delay(1000 * o);
            }, () =>
            {
                Console.WriteLine("OnCompleted on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
                are.Set();
            }))
        {
            subject.OnNext(3);
            subject.OnNext(2);
            subject.OnNext(1);
            subject.OnCompleted();
            are.WaitOne();
        }
}
Console.WriteLine("Done.");