简体   繁体   English

C# Rx Observable 产生随机结果

[英]C# Rx Observable producing random results

Consider the following program;考虑以下程序;

class Program
{
    static IObservable<int> GetNumbers()
    {
        var observable = Observable.Empty<int>();
        foreach (var i in Enumerable.Range(1, 10))
        {
            observable = observable.Concat(Observable.FromAsync(() => Task.Run(() =>
            {
                Console.WriteLine($"Producing {i}");
                Thread.Sleep(1000);
                return i;
            })));
        }

        return observable;
    }

    static async Task LogNumbers(IObservable<int> observable)
    {
        var subscription = observable.Subscribe(i => Console.WriteLine($"Consuming {i}"));
        await observable;
        subscription.Dispose();
    }

    static void Main(string[] args)
    {
        LogNumbers(GetNumbers()).Wait();
        Console.WriteLine("Finished");
        Console.ReadLine();
    }
}

It produces the following output它产生以下 output

Producing 1
Producing 1
Producing 2
Consuming 1
Producing 2
Producing 3
Consuming 2
Producing 3
Producing 4
Consuming 3
Producing 4
Producing 5
Consuming 4
Producing 5
Producing 6
Consuming 5
Producing 6
Producing 7
Consuming 6
Producing 7
Producing 8
Consuming 7
Producing 8
Producing 9
Consuming 8
Producing 9
Producing 10
Consuming 9
Producing 10
Finished

It writes out two of every "Producing x" statements and one "Consuming x" statement.它写出每个“生产 x”语句中的两个和一个“消费 x”语句。 Why does it do this?为什么这样做? Why does it never write out the expected final "Consuming 10" statement?为什么它永远不会写出预期的最终“消费 10”语句?

You are getting two copies of the Producing lines because you are subscribing twice.由于您订阅了两次,因此您将获得两份生产线副本。 Most likely, you are not getting the consuming 10 because the first subscription is being cancelled when the second subscription ends.最有可能的是,您没有得到消耗的 10,因为当第二个订阅结束时,第一个订阅被取消。 I would not be surprised if you sometimes did get the Consuming 10, just because the Tasks run in a different order that time.如果您有时确实获得了 Consuming 10,我不会感到惊讶,只是因为当时任务以不同的顺序运行。

static async Task LogNumbers(IObservable<int> observable)
{
    //This is the first subscription
    var subscription = observable.Subscribe(i => Console.WriteLine($"Consuming {i}"));

    //This is the second subscription
    await observable;

    subscription.Dispose();
}

The way your GetNumbers function is written, each subscription to the observable will trigger its own set of 10 tasks to run, and thus its own set of outputs.您的GetNumbers function 的编写方式是,每次订阅 observable 都会触发其自己的 10 个任务集运行,从而触发其自己的输出集。 The first subscription also monitors the produced values and outputs a Consuming line.第一个订阅还监视生成的值并输出消耗行。 The second subscription does nothing with the produced values, since you did not use the value of await observable , but does cause a second set of tasks to run.第二个订阅对生成的值没有任何作用,因为您没有使用await observable的值,但确实会导致第二组任务运行。

You could eliminate the second subscription by either using Publish().RefCount() on the parameter to LogNumbers or by instead using a TaskCompletionSource and marking it complete from the OnError and OnComplete functions you currently aren't using in the first subscription.您可以通过在 LogNumbers 的参数上使用Publish().RefCount()或使用 TaskCompletionSource 并从您当前未在第一个订阅中使用的 OnError 和 OnComplete 函数将其标记为完成来消除第二个订阅。 Those would look something like this:那些看起来像这样:

static async Task LogNumbersWithRefCount(IObservable<int> observable)
{
    observable = observable.Publish().RefCount();
    var subscription = observable.Subscribe(i => Console.WriteLine($"Consuming {i}"));
    await observable;
    subscription.Dispose();
}

static async Task LogNumbersTCS(IObservable<int> observable)
{
    var t = new TaskCompletionSource<object>()
    var subscription = observable.Subscribe(i => Console.WriteLine($"Consuming {i}"),
                       ex => t.TrySetException(ex),
                       () => t.TrySetResult(null));
    return t.Task;
}

Gideon nailed the issue for you, but as I started putting some hints in the comments I thought it might be good to post a complete solution. Gideon 为您解决了这个问题,但是当我开始在评论中提出一些提示时,我认为发布一个完整的解决方案可能会很好。 Try this:尝试这个:

static IObservable<int> GetNumbers() =>
    Observable
        .Interval(TimeSpan.FromSeconds(1.0))
        .Select(i => (int)i + 1)
        .Do(i => Console.WriteLine($"Producing {i}"))
        .Take(10);

static Task LogNumbers(IObservable<int> observable) =>
    observable
        .Do(i => Console.WriteLine($"Consuming {i}"))
        .ToArray()
        .ToTask();

static void Main(string[] args)
{
    LogNumbers(GetNumbers()).Wait();
    Console.WriteLine("Finished");
    Console.ReadLine();
}

Or, even more cleanly:或者,更干净:

static IObservable<int> GetNumbers() =>
    Observable
        .Interval(TimeSpan.FromSeconds(1.0))
        .Select(i => (int)i + 1)
        .Do(i => Console.WriteLine($"Producing {i}"))
        .Take(10);

static IObservable<int> LogNumbers(IObservable<int> observable) =>
    observable
        .Do(i => Console.WriteLine($"Consuming {i}"));

static async Task Main(string[] args)
{
    await LogNumbers(GetNumbers());
    Console.WriteLine("Finished");
    Console.ReadLine();
}

You can await observables directly.您可以直接await可观察对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM