结合最新的可观察的可观察量

时间:2014-02-06 05:09:43

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

假设我有一组我正在监控可用性的URI。每个URI都是“向上”或“向下”,并且可以随时向系统添加要监视的新URI:

public enum ConnectionStatus
{
    Up,
    Down
}

public class WebsiteStatus
{
    public string Uri
    {
        get;
        set;
    }

    public ConnectionStatus Status
    {
        get;
        set;
    }
}

public class Program
{
    static void Main(string[] args)
    {
        var statusStream = new Subject<WebsiteStatus>();
        Test(statusStream);

        Console.WriteLine("Done");
        Console.ReadKey();
    }

    private static void Test(IObservable<WebsiteStatus> statusStream)
    {
    }
}

现在假设Test()我想反应性地确定:

  • 是否所有URI都已关闭(作为bool
  • 哪些URI已关闭(为IEnumerable<string>

因此Test最终会创建一个类似IObservable<Tuple<bool, IEnumerable<string>>>的可观察对象,其中bool表示所有URI都已关闭且IEnumerable<string>包含那些URI。

我该如何解决这个问题?我最初的想法是,我需要按URI分组,然后将每个组中的最新组合成一个列表,然后我可以执行Select反对。但是,由于CombineLatest的工作方式,这种情况无法解决。

编辑:感谢Matthew的回答,我查看了rxx并发现它实现了一个CombineLatest重载,完全按照我在rx开箱即期的方式实现,除了我需要更改它以便即使只有一个源流被组合时它也会发布(默认情况下,它等待至少两个源流)。此外,我无法为一种方法提供额外的2MB二进制文件,因此我将其复制/粘贴到我的项目中。这样做,我能够解决如下:

private static void Test(IObservable<WebsiteStatus> statusStream)
{
    statusStream
        .GroupBy(x => x.Uri)
        .CombineLatest()
        .Select(
            x =>
            {
                var down = x.Where(y => y.Status == ConnectionStatus.Down);
                var downCount = down.Count();
                var downUris = down.Select(y => y.Uri).ToList();

                return new
                {
                    AllDown = x.Count == downCount,
                    DownUris = downUris
                };
            })
        .Subscribe(x =>
        {
            Console.WriteLine("    Sources down ({0}): {1}", x.AllDown ? "that's all of them" : "some are still up", x.DownUris.Aggregate("", (y, z) => y += (z + " | ")));
        });
}

1 个答案:

答案 0 :(得分:4)

最好的方法是在this answer中使用Rxx扩展名。下面是另一种选择,它只是保留了一个向下/向上的网站列表。

var downStream = statusStream
    .Aggregate<WebsiteStatus, IEnumerable<string>>(new string[0], (down, newStatus) =>
    {
        if (newStatus.IsUp)
            return down.Where(uri => uri != newStatus.Uri);
        else if (!down.Contains(newStatus.Uri))
            return down.Concat(new string[] { newStatus.Uri });
        else
            return down;
    });

var upStream = statusStream
    .Aggregate<WebsiteStatus, IEnumerable<string>>(new string[0], (up, newStatus) =>
    {
        if (!newStatus.IsUp)
            return up.Where(uri => uri != newStatus.Uri);
        else if (!up.Contains(newStatus.Uri))
            return down.Concat(new string[] { newStatus.Uri });
        else
            return up;
    });

var allDown = upStream.Select(up => !up.Any());