C# 缓冲观测值排序

C# 缓冲观测值排序,c#,system.reactive,tpl-dataflow,C#,System.reactive,Tpl Dataflow,我有一个令牌流,生成速度非常快,而处理器相对较慢。令牌有三个子类型,我更希望它们按优先级处理。因此,我希望令牌在生成并等待处理后被缓冲,并按优先级对缓冲区进行排序 以下是我的课程: public enum Priority { High = 3, Medium = 2, Low = 1 } public class Base : IComparable<Base> { public int Id { get; set; } pub

我有一个令牌流,生成速度非常快,而处理器相对较慢。令牌有三个子类型,我更希望它们按优先级处理。因此,我希望令牌在生成并等待处理后被缓冲,并按优先级对缓冲区进行排序

以下是我的课程:

public enum Priority
{
    High   = 3,
    Medium = 2,
    Low    = 1
}

public class Base : IComparable<Base>
{
    public int Id { get; set; }

    public int CompareTo(Base other)
    {
        return Id.CompareTo(other.Id);
    }
}

public class Foo : Base { }
public class Bar : Base { }
public class Baz : Base { }

public class Token : IComparable<Token>
{
    private readonly string _toString;

    public Foo Foo { get; }

    public Bar Bar { get; }

    public Baz Baz { get; }

    public Priority Priority =>
        Baz == null
            ? Bar == null
                ? Priority.High
                : Priority.Medium
            : Priority.Low;

    public int CompareTo(Token other)
    {
        if (Priority > other.Priority)
        {
            return -1;
        }

        if (Priority < other.Priority)
        {
            return 1;
        }

        switch (Priority)
        {
            case Priority.High:
                return Foo.CompareTo(other.Foo);
            case Priority.Medium:
                return Bar.CompareTo(other.Bar);
            case Priority.Low:
                return Baz.CompareTo(other.Baz);
            default:
                throw new ArgumentOutOfRangeException();
        }
    }

    public override string ToString()
    {
        return _toString;
    }

    public Token(Foo foo)
    {
        _toString = $"{nameof(Foo)}:{foo.Id}";
        Foo = foo;
    }

    public Token(Foo foo, Bar bar) : this(foo)
    {
        _toString += $":{nameof(Bar)}:{bar.Id}";
        Bar = bar;
    }

    public Token(Foo foo, Baz baz) : this(foo)
    {
        _toString += $":{nameof(Baz)}:{baz.Id}";
        Baz = baz;
    }
}
然而,对于如何缓冲和排序令牌,我有点困惑。如果我这样做的话,代币看起来是同时生产和消费的,这表明在幕后有一些缓冲,但我无法控制它

tokens.Subscribe(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
});
如果我使用TPL数据流
ActionBlock
,我可以看到令牌被正确地生成和处理,但我仍然不确定如何进行排序

var proc = new ActionBlock<Token>(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
});

tokens.Subscribe(dt => proc.Post(dt));
我添加了一个
SortedSet

var set = new SortedSet<Token>();

var tokens = bazTokens
    .Merge(barTokens)
    .Merge(fooTokens)
    .Do(dt => Display(dt, ConsoleColor.Red));

tokens.Subscribe(dt => set.Add(dt));

所以,现在我得到了我想要的结果,但是a)我对
轮询不满意,b)如果我想要多个消费者,我会遇到竞争条件。所以,我仍然在寻找更好的实现,如果有人有一个

> P>您想要的容器是优先级队列,不幸的是.NET运行时没有实现(C++中的STL/CLI,但PrimyItyQueLe没有其他语言可用)。
有一些现有的非MS容器填充此角色,您需要搜索并查看结果,以选择一个满足您需要的容器。

使用数据流,您可以过滤令牌,以便每个优先级在管道中沿不同的路径。通过在每个优先级类型的链接上使用谓词来过滤标记。然后取决于你如何根据优先级给予优先权

排序:

var highPriority = new ActionBlock<Token>(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
});

var midPriority = new ActionBlock<Token>(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
});

var lowPriority = new ActionBlock<Token>(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
});

var proc = new BufferBlock<Token>();

proc.LinkTo(highPriority, dt => dt.Priority == Priority.High);
proc.LinkTo(midPriority, dt => dt.Priority == Priority.Medium);
proc.LinkTo(lowPriority, dt => dt.Priority == Priority.Low);

tokens.Subscribe(dt => proc.Post(dt));
var highPriOptions = new DataflowLinkOptions(){MaxDegreeOfParallelism = 3}
var highPriority = new ActionBlock<Token>(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
}, highPriOptions);

var midPriOptions = new DataflowLinkOptions(){MaxDegreeOfParallelism = 2}   
var midPriority = new ActionBlock<Token>(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
}, midPriOptions);

var lowPriority = new ActionBlock<Token>(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
});

var proc = new BufferBlock<Token>();

proc.LinkTo(highPriority, dt => dt.Priority == Priority.High);
proc.LinkTo(midPriority, dt => dt.Priority == Priority.Medium);
proc.LinkTo(lowPriority, dt => dt.Priority == Priority.Low);

tokens.Subscribe(dt => proc.Post(dt));

这些样本绝对不完整,但至少应该给你一个想法。

好的,所以我使用了一个普通的
锁来访问
分类数据集
,然后增加了消费者的数量,它似乎工作得很好,因此,尽管我还不能提出完整的RX或拆分RX/TPL数据流解决方案,但现在这正是我想要的,所以我将在原始问题中显示除了更新之外所做的更改,并将其保留在那里

var set = new SortedSet<Token>();
var locker = new object();

var tokens = bazTokens
    .Merge(barTokens)
    .Merge(fooTokens)
    .Do(dt => Display(dt, ConsoleColor.Red));

tokens.Subscribe(dt =>
{
    lock (locker)
    {
        set.Add(dt);
    }
});

for (var i = 0; i < Environment.ProcessorCount; i++)
{
    Task.Run(() =>
    {
        while (!source.IsCancellationRequested)
        {
            Token dt;

            lock (locker)
            {
                dt = set.FirstOrDefault();
            }

            if (dt == null)
            {
                continue;
            }

            bool removed;

            lock (locker)
            {
                removed = set.Remove(dt);
            }

            if (removed)
            {
                Display(dt, ConsoleColor.Green, 750);
            }
        }
    }, source.Token);
}
var set=new SortedSet();
var locker=新对象();
var tokens=bazTokens
.合并(巴托克斯)
.合并(fooTokens)
.Do(dt=>显示(dt,控制台颜色.Red));
tokens.Subscribe(dt=>
{
锁(储物柜)
{
加(dt);
}
});
对于(变量i=0;i
{
而(!source.IsCancellationRequested)
{
令牌dt;
锁(储物柜)
{
dt=set.FirstOrDefault();
}
如果(dt==null)
{
继续;
}
去除bool;
锁(储物柜)
{
移除=设置移除(dt);
}
如果(已删除)
{
显示器(dt,控制台颜色,绿色,750);
}
}
},source.Token);
}

感谢发布解决方案的人员,我感谢您花费的时间。

我认为这里的难题是,您似乎真正想要的是基于快速、热门、推送源的拉动模式的结果。您似乎想要的是迄今为止收到的“最高”优先级,但问题是“收到的是什么?”如果您有多个订阅者,以不同的速度运行,他们可以各自对“最高”是什么有自己的看法

因此,我的看法是,您希望将源合并到一种反应式的、按优先级排序(排序)的队列中,在观察者准备就绪时从中提取结果

我通过使用返回缓冲区的信号来实现这一点,说“我的一个观察者现在准备好查看优先列表的状态”。这是通过使用接收可观察到的关闭信号的缓冲过载来实现的。该缓冲区包含接收到的元素的新列表,我刚刚将其合并到最后一个列表中,即sans'highest'

出于本问题的目的,该代码只是演示拼凑的代码-可能存在错误:

 using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RxTests
{
    class Program
    {
        static void Main(string[] args)
        {
            var p = new Program();
            p.TestPrioritisedBuffer();
            Console.ReadKey();


        }

        void TestPrioritisedBuffer()
        {
            var source1 = Observable.Interval(TimeSpan.FromSeconds(1)).Do((source) => Console.WriteLine("Source1:"+source));
            var source2 = Observable.Interval(TimeSpan.FromSeconds(5)).Scan((x,y)=>(x+100)).Do((source) => Console.WriteLine("Source2:" + source)); ;

            BehaviorSubject<bool> closingSelector = new BehaviorSubject<bool>(true);



            var m = Observable.Merge(source1, source2).
                Buffer(closingSelector).
                Select(s => new { list =s.ToList(), max=(long)0 }).
               Scan((x, y) =>
               {
                   var list = x.list.Union(y.list).OrderBy(k=>k);

                   var max = list.LastOrDefault();


                   var res = new
                   {
                       list = list.Take(list.Count()-1).ToList(),
                       max= max
                   };

                   return res;



               }
               ).
               Do((sorted) => Console.WriteLine("Sorted max:" + sorted.max + ".  Priority queue length:" + sorted.list.Count)).
               ObserveOn(Scheduler.Default); //observe on other thread

            m.Subscribe((v)=> { Console.WriteLine("Observed: "+v.max); Thread.Sleep(3000); closingSelector.OnNext(true); }) ;
        }
    }
}
使用系统;
使用System.Collections.Concurrent;
使用System.Collections.Generic;
使用System.Linq;
使用System.Reactive.Concurrency;
使用System.Reactive.Linq;
使用系统、反应、主题;
使用系统文本;
使用系统线程;
使用System.Threading.Tasks;
名称空间RxTests
{
班级计划
{
静态void Main(字符串[]参数)
{
var p=新程序();
p、 testprioritizedbuffer();
Console.ReadKey();
}
void testprioritizedbuffer()
{
var source1=Observable.Interval(TimeSpan.FromSeconds(1)).Do((source)=>Console.WriteLine(“source1:+source”);
var source2=可观察的.Interval(TimeSpan.FromSeconds(5)).Scan((x,y)=>(x+100)).Do((source)=>Console.WriteLine(“source2:+source”);
BehaviorSubject closingSelector=新的BehaviorSubject(true);
var m=可观察的.Merge(source1,source2)。
缓冲区(关闭选择器)。
选择(s=>new{list=s.ToList(),max=(long)0})。
扫描((x,y)=>
{
var list=x.list.Union(y.list).OrderBy(k=>k);
var max=list.LastOrDefault();
var res=新
{
list=list.Take(list.Count()-1).ToList(),
最大值=最大值
};
返回res;
}
).
Do((排序)=>Console.WriteLine(“排序最大值:+sorted.max+”。优先级队列长度:+sorted.list.Count))。
ObserveOn(Scheduler.Default);//在其他线程上观察
m、 Subscribe((v)=>{Console.WriteLine(“已观察:+v.max”);Thread.Sleep(3000);closingSelec
var highPriOptions = new DataflowLinkOptions(){MaxDegreeOfParallelism = 3}
var highPriority = new ActionBlock<Token>(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
}, highPriOptions);

var midPriOptions = new DataflowLinkOptions(){MaxDegreeOfParallelism = 2}   
var midPriority = new ActionBlock<Token>(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
}, midPriOptions);

var lowPriority = new ActionBlock<Token>(dt =>
{
    Thread.Sleep(TimeSpan.FromMilliseconds(250));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine($"{DateTime.Now:mm:ss.fff}:{dt}");
});

var proc = new BufferBlock<Token>();

proc.LinkTo(highPriority, dt => dt.Priority == Priority.High);
proc.LinkTo(midPriority, dt => dt.Priority == Priority.Medium);
proc.LinkTo(lowPriority, dt => dt.Priority == Priority.Low);

tokens.Subscribe(dt => proc.Post(dt));
var set = new SortedSet<Token>();
var locker = new object();

var tokens = bazTokens
    .Merge(barTokens)
    .Merge(fooTokens)
    .Do(dt => Display(dt, ConsoleColor.Red));

tokens.Subscribe(dt =>
{
    lock (locker)
    {
        set.Add(dt);
    }
});

for (var i = 0; i < Environment.ProcessorCount; i++)
{
    Task.Run(() =>
    {
        while (!source.IsCancellationRequested)
        {
            Token dt;

            lock (locker)
            {
                dt = set.FirstOrDefault();
            }

            if (dt == null)
            {
                continue;
            }

            bool removed;

            lock (locker)
            {
                removed = set.Remove(dt);
            }

            if (removed)
            {
                Display(dt, ConsoleColor.Green, 750);
            }
        }
    }, source.Token);
}
 using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RxTests
{
    class Program
    {
        static void Main(string[] args)
        {
            var p = new Program();
            p.TestPrioritisedBuffer();
            Console.ReadKey();


        }

        void TestPrioritisedBuffer()
        {
            var source1 = Observable.Interval(TimeSpan.FromSeconds(1)).Do((source) => Console.WriteLine("Source1:"+source));
            var source2 = Observable.Interval(TimeSpan.FromSeconds(5)).Scan((x,y)=>(x+100)).Do((source) => Console.WriteLine("Source2:" + source)); ;

            BehaviorSubject<bool> closingSelector = new BehaviorSubject<bool>(true);



            var m = Observable.Merge(source1, source2).
                Buffer(closingSelector).
                Select(s => new { list =s.ToList(), max=(long)0 }).
               Scan((x, y) =>
               {
                   var list = x.list.Union(y.list).OrderBy(k=>k);

                   var max = list.LastOrDefault();


                   var res = new
                   {
                       list = list.Take(list.Count()-1).ToList(),
                       max= max
                   };

                   return res;



               }
               ).
               Do((sorted) => Console.WriteLine("Sorted max:" + sorted.max + ".  Priority queue length:" + sorted.list.Count)).
               ObserveOn(Scheduler.Default); //observe on other thread

            m.Subscribe((v)=> { Console.WriteLine("Observed: "+v.max); Thread.Sleep(3000); closingSelector.OnNext(true); }) ;
        }
    }
}