在进程终止之前确保完成异步OnNext代码

时间:2016-09-13 22:10:45

标签: c# system.reactive

下面的单元测试永远不会打印" Async 3"因为测试首先完成。我怎样才能确保它完成?我能想到的最好的是最后一个任意的Task.Delay或WriteAsync()。结果,都不是理想的。

    public async Task TestMethod1() // eg. webjob
    {
        TestContext.WriteLine("Starting test...");
        var observable = Observable.Create<int>(async ob =>
        {
            ob.OnNext(1);
            await Task.Delay(1000); // Fake async REST api call
            ob.OnNext(2);
            await Task.Delay(1000);
            ob.OnNext(3);
            ob.OnCompleted();
        });

        observable.Subscribe(i => TestContext.WriteLine($"Sync {i}"));
        observable.SelectMany(i => WriteAsync(i).ToObservable()).Subscribe();

        await observable;
        TestContext.WriteLine("Complete.");
    }

    public async Task WriteAsync(int value) // Fake async DB call
    {
        await Task.Delay(1000);
        TestContext.WriteLine($"Async {value}");
    }

修改 我意识到提到单元测试可能会产生误导。这不是一个测试问题。该代码模拟了Azure WebJob中运行的进程的实际问题,其中生产者和使用者都需要调用某些Async IO。问题是webjob在消费者真正完成之前就已经完成了。这是因为我无法弄清楚如何正确等待消费者方面的任何事情。也许这对RX来说是不可能的......

2 个答案:

答案 0 :(得分:1)

修改: 您基本上正在寻找阻止运营商。旧的阻塞运算符(如DECLARE @Dt NVARCHAR(255) SET @Dt = '2016-04-25T00:00:00' SELECT CONVERT(NVARCHAR(10), CONVERT(DATE, CAST(FLOOR(CAST(CONVERT(DATETIME, @Dt) AS FLOAT)) AS DATETIME), 103), 103) )已弃用,以支持异步版本。你想等待最后一项:

ForEach

虽然这可以解决您的直接问题,但由于以下情况,您似乎仍会继续遇到问题(我还增加了两个)。当使用正确时,Rx非常强大,而当没有使用时,它会让人感到困惑。

旧答案:

一些事情:

  1. 混合async / await和Rx通常会导致两者的陷阱和两者的好处。
  2. Rx具有强大的测试功能。你没有使用它。
  3. 副作用(如public async Task TestMethod1() { TestContext.WriteLine("Starting test..."); var observable = Observable.Create<int>(async ob => { ob.OnNext(1); await Task.Delay(1000); ob.OnNext(2); await Task.Delay(1000); ob.OnNext(3); ob.OnCompleted(); }); observable.Subscribe(i => TestContext.WriteLine($"Sync {i}")); var selectManyObservable = observable.SelectMany(i => WriteAsync(i).ToObservable()).Publish().RefCount(); selectManyObservable.Subscribe(); await selectManyObservable.LastOrDefaultAsync(); TestContext.WriteLine("Complete."); } )最好仅在订阅中执行,而不是在WriteLine等运算符中执行。
  4. 您可能希望了解冷与热的观察结果。
  5. 它没有运行完成的原因是因为你的测试运行器。您的测试运行员在SelectMany结束时终止测试。否则,Rx订阅将继续存在。当我在Linqpad中运行代码时,我得到以下输出:

    开始测试...
    同步1
    同步2
    异步1
    同步3
    异步2
    完整。
    Async 3

  6. ...这是我假设您想要看到的内容,除非您可能希望在Async 3之后完成。

    仅使用Rx,您的代码看起来像这样:

    TestMethod1

    这仍然没有利用Rx的测试功能。这看起来像这样:

    public void TestMethod1()
    {
        TestContext.WriteLine("Starting test...");
        var observable = Observable.Concat<int>(
            Observable.Return(1),
            Observable.Empty<int>().Delay(TimeSpan.FromSeconds(1)),
            Observable.Return(2),
            Observable.Empty<int>().Delay(TimeSpan.FromSeconds(1)),
            Observable.Return(3)
        );
    
        var syncOutput = observable
            .Select(i => $"Sync {i}");
        syncOutput.Subscribe(s => TestContext.WriteLine(s));
    
        var asyncOutput = observable
            .SelectMany(i => WriteAsync(i, scheduler));
        asyncOutput.Subscribe(s => TestContext.WriteLine(s), () => TestContext.WriteLine("Complete."));
    }
    
    public IObservable<string> WriteAsync(int value, IScheduler scheduler)
    {
        return Observable.Return(value)
            .Delay(TimeSpan.FromSeconds(1), scheduler)
            .Select(i => $"Async {value}");
    }
    
    
    public static class TestContext
    {
        public static void WriteLine(string s)
        {
            Console.WriteLine(s);
        }
    }
    

    ...因此,与您的任务测试不同,您不必等待。测试立即执行。您可以将延迟时间提高到几分钟或几小时,而public void TestMethod1() { var scheduler = new TestScheduler(); TestContext.WriteLine("Starting test..."); var observable = Observable.Concat<int>( Observable.Return(1), Observable.Empty<int>().Delay(TimeSpan.FromSeconds(1), scheduler), Observable.Return(2), Observable.Empty<int>().Delay(TimeSpan.FromSeconds(1), scheduler), Observable.Return(3) ); var syncOutput = observable .Select(i => $"Sync {i}"); syncOutput.Subscribe(s => TestContext.WriteLine(s)); var asyncOutput = observable .SelectMany(i => WriteAsync(i, scheduler)); asyncOutput.Subscribe(s => TestContext.WriteLine(s), () => TestContext.WriteLine("Complete.")); var asyncExpected = scheduler.CreateColdObservable<string>( ReactiveTest.OnNext(1000.Ms(), "Async 1"), ReactiveTest.OnNext(2000.Ms(), "Async 2"), ReactiveTest.OnNext(3000.Ms(), "Async 3"), ReactiveTest.OnCompleted<string>(3000.Ms() + 1) //+1 because you can't have two notifications on same tick ); var syncExpected = scheduler.CreateColdObservable<string>( ReactiveTest.OnNext(0000.Ms(), "Sync 1"), ReactiveTest.OnNext(1000.Ms(), "Sync 2"), ReactiveTest.OnNext(2000.Ms(), "Sync 3"), ReactiveTest.OnCompleted<string>(2000.Ms()) //why no +1 here? ); var asyncObserver = scheduler.CreateObserver<string>(); asyncOutput.Subscribe(asyncObserver); var syncObserver = scheduler.CreateObserver<string>(); syncOutput.Subscribe(syncObserver); scheduler.Start(); ReactiveAssert.AreElementsEqual( asyncExpected.Messages, asyncObserver.Messages); ReactiveAssert.AreElementsEqual( syncExpected.Messages, syncObserver.Messages); } public static class MyExtensions { public static long Ms(this int ms) { return TimeSpan.FromMilliseconds(ms).Ticks; } } 基本上会模拟您的时间。然后你的测试运动员可能会很高兴。

答案 1 :(得分:-1)

好吧,您可以使用Observable.ForEach阻止,直到.png终止:

IObservable

您可以使observable.ForEach(unusedValue => { }); 成为普通的非异步方法,然后用此替换TestMethod1吗?