C# TPL数据流广播块丢弃最后一条消息

C# TPL数据流广播块丢弃最后一条消息,c#,.net,unit-testing,tpl-dataflow,C#,.net,Unit Testing,Tpl Dataflow,我有一个很简单的问题。我需要一种方法来轻松地对需要一些时间的消息执行一些处理。在处理过程中,可能会输入新请求,但可以丢弃除最后一个请求之外的所有请求 因此,我认为TPLBroadcastblock应该做到这一点,比如查看StackExchange上的文档和帖子。我创建了以下解决方案,并为其添加了一些单元测试,但在单元测试中,有时最后一项没有发送 这不是我所期望的。如果它应该删除任何内容,我会说它应该删除第一项,因为如果它不能处理消息,它应该覆盖它的缓冲区1。有人能看到它是什么吗? 任何帮助都将不

我有一个很简单的问题。我需要一种方法来轻松地对需要一些时间的消息执行一些处理。在处理过程中,可能会输入新请求,但可以丢弃除最后一个请求之外的所有请求

因此,我认为TPL
Broadcastblock
应该做到这一点,比如查看StackExchange上的文档和帖子。我创建了以下解决方案,并为其添加了一些单元测试,但在单元测试中,有时最后一项没有发送

这不是我所期望的。如果它应该删除任何内容,我会说它应该删除第一项,因为如果它不能处理消息,它应该覆盖它的缓冲区1。有人能看到它是什么吗?
任何帮助都将不胜感激

下面是块的代码:

/// <summary>
/// This block will take items and perform the specified action on it. Any incoming messages while the action is being performed
/// will be discarded.
/// </summary>
public class DiscardWhileBusyActionBlock<T> : ITargetBlock<T>
{
    private readonly BroadcastBlock<T> broadcastBlock;

    private readonly ActionBlock<T> actionBlock;

    /// <summary>
    /// Initializes a new instance of the <see cref="DiscardWhileBusyActionBlock{T}"/> class.
    /// Constructs a SyncFilterTarget{TInput}.
    /// </summary>
    /// <param name="actionToPerform">Thing to do.</param>
    public DiscardWhileBusyActionBlock(Action<T> actionToPerform)
    {
        if (actionToPerform == null)
        {
            throw new ArgumentNullException(nameof(actionToPerform));
        }

        this.broadcastBlock = new BroadcastBlock<T>(item => item);
        this.actionBlock = new ActionBlock<T>(actionToPerform, new ExecutionDataflowBlockOptions { BoundedCapacity = 1, MaxDegreeOfParallelism = 1 });
        this.broadcastBlock.LinkTo(this.actionBlock);
        this.broadcastBlock.Completion.ContinueWith(task => this.actionBlock.Complete());
    }

    public DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> source, bool consumeToAccept)
    {
        return ((ITargetBlock<T>)this.broadcastBlock).OfferMessage(messageHeader, messageValue, source, consumeToAccept);
    }

    public void Complete()
    {
        this.broadcastBlock.Complete();
    }

    public void Fault(Exception exception)
    {
        ((ITargetBlock<T>)this.broadcastBlock).Fault(exception);
    }

    public Task Completion => this.actionBlock.Completion;
}
//
///此块将获取项目并对其执行指定的操作。执行操作时收到的任何消息
///将被丢弃。
/// 
公共类DiscardWhileBusyActionBlock:ITargetBlock
{
私有只读广播块广播块;
私有只读ActionBlock ActionBlock;
/// 
///初始化类的新实例。
///构造SyncFilterTarget{TInput}。
/// 
///要做的事。
公共丢弃WhileBusyActionBlock(操作执行)
{
if(actionToPerform==null)
{
抛出新ArgumentNullException(nameof(actionToPerform));
}
this.broadcastBlock=新的broadcastBlock(item=>item);
this.actionBlock=new actionBlock(actionToPerform,new ExecutionDataflowBlockOptions{BoundedCapacity=1,MaxDegreeOfParallelism=1});
this.broadcastBlock.LinkTo(this.actionBlock);
this.broadcastBlock.Completion.ContinueWith(task=>this.actionBlock.Complete());
}
消息的公共DataflowMessageStatus(DataflowMessageHeader消息头、T消息值、ISourceBlock源、bool consumeToAccept)
{
return((ITargetBlock)this.broadcastBlock).OfferMessage(messageHeader、messageValue、source、consumeToAccept);
}
公开作废完成()
{
this.broadcastBlock.Complete();
}
公共无效错误(例外)
{
((ITargetBlock)this.broadcastBlock).错误(异常);
}
公共任务完成=>this.actionBlock.Completion;
}
下面是测试代码:

[TestClass]
public class DiscardWhileBusyActionBlockTest
{
    [TestMethod]
    public void PostToConnectedBuffer_ActionNotBusy_MessageConsumed()
    {
        var actionPerformer = new ActionPerformer();

        var block = new DiscardWhileBusyActionBlock<int>(actionPerformer.Perform);
        var buffer = DiscardWhileBusyActionBlockTest.SetupBuffer(block);

        buffer.Post(1);

        DiscardWhileBusyActionBlockTest.WaitForCompletion(buffer, block);

        var expectedMessages = new[] { 1 };
        actionPerformer.LastReceivedMessage.Should().BeEquivalentTo(expectedMessages);
    }

    [TestMethod]
    public void PostToConnectedBuffer_ActionBusy_MessagesConsumedWhenActionBecomesAvailable()
    {
        var actionPerformer = new ActionPerformer();

        var block = new DiscardWhileBusyActionBlock<int>(actionPerformer.Perform);
        var buffer = DiscardWhileBusyActionBlockTest.SetupBuffer(block);

        actionPerformer.SetBusy();

        // 1st message will set the actionperformer to busy, 2nd message should be sent when
        // it becomes available.
        buffer.Post(1);
        buffer.Post(2);

        actionPerformer.SetAvailable();

        DiscardWhileBusyActionBlockTest.WaitForCompletion(buffer, block);

        var expectedMessages = new[] { 1, 2 };
        actionPerformer.LastReceivedMessage.Should().BeEquivalentTo(expectedMessages);
    }

    [TestMethod]
    public void PostToConnectedBuffer_ActionBusy_DiscardMessagesInBetweenAndProcessOnlyLastMessage()
    {
        var actionPerformer = new ActionPerformer();

        var block = new DiscardWhileBusyActionBlock<int>(actionPerformer.Perform);
        var buffer = DiscardWhileBusyActionBlockTest.SetupBuffer(block);

        actionPerformer.SetBusy();

        buffer.Post(1);
        buffer.Post(2);
        buffer.Post(3);
        buffer.Post(4);
        buffer.Post(5);

        actionPerformer.SetAvailable();

        DiscardWhileBusyActionBlockTest.WaitForCompletion(buffer, block);

        var expectedMessages = new[] { 1, 5 };
        actionPerformer.LastReceivedMessage.Should().BeEquivalentTo(expectedMessages);
    }

    private static void WaitForCompletion(IDataflowBlock source, IDataflowBlock target)
    {
        source.Complete();
        target.Completion.Wait(TimeSpan.FromSeconds(1));
    }

    private static BufferBlock<int> SetupBuffer(ITargetBlock<int> block)
    {
        var buffer = new BufferBlock<int>();
        buffer.LinkTo(block);
        buffer.Completion.ContinueWith(task => block.Complete());
        return buffer;
    }

    private class ActionPerformer
    {
        private readonly ManualResetEvent resetEvent = new ManualResetEvent(true);

        public List<int> LastReceivedMessage { get; } = new List<int>();

        public void Perform(int message)
        {
            this.resetEvent.WaitOne(TimeSpan.FromSeconds(3));
            this.LastReceivedMessage.Add(message);
        }

        public void SetBusy()
        {
            this.resetEvent.Reset();
        }

        public void SetAvailable()
        {
            this.resetEvent.Set();
        }
    }
}
[TestClass]
公共类丢弃WhileBusyActionBlockTest
{
[测试方法]
public void PostToConnectedBuffer\u action notbusy\u messageconsumered()已使用
{
var actionPerformer=新的actionPerformer();
var block=新的丢弃BusyActionBlock(actionPerformer.Perform);
var buffer=DiscardWhileBusyActionBlockTest.SetupBuffer(块);
缓冲柱(1);
丢弃BusyActionBlockTest.WaitForCompletion(缓冲区、块);
var expectedMessages=new[]{1};
actionPerformer.LastReceivedMessage.Should().BeEquivalentTo(expectedMessages);
}
[测试方法]
public void PostToConnected Buffer\u Action Busy\u Messages在连接变得可用时使用()
{
var actionPerformer=新的actionPerformer();
var block=新的丢弃BusyActionBlock(actionPerformer.Perform);
var buffer=DiscardWhileBusyActionBlockTest.SetupBuffer(块);
actionPerformer.SetBusy();
//第一条消息将actionperformer设置为忙碌,第二条消息应在
//它变得可用。
缓冲柱(1);
缓冲区.邮政(2);;
actionPerformer.SetAvailable();
丢弃BusyActionBlockTest.WaitForCompletion(缓冲区、块);
var expectedMessages=new[]{1,2};
actionPerformer.LastReceivedMessage.Should().BeEquivalentTo(expectedMessages);
}
[测试方法]
public void PostToConnectedBuffer\u action busy\u丢弃介于和ProcessOnlyLastMessage()之间的消息
{
var actionPerformer=新的actionPerformer();
var block=新的丢弃BusyActionBlock(actionPerformer.Perform);
var buffer=DiscardWhileBusyActionBlockTest.SetupBuffer(块);
actionPerformer.SetBusy();
缓冲柱(1);
缓冲区.邮政(2);;
缓冲柱(3);
缓冲区。邮政(4);
缓冲柱(5);
actionPerformer.SetAvailable();
丢弃BusyActionBlockTest.WaitForCompletion(缓冲区、块);
var expectedMessages=new[]{1,5};
actionPerformer.LastReceivedMessage.Should().BeEquivalentTo(expectedMessages);
}
私有静态void WaitForCompletion(IDataflowBlock源、IDataflowBlock目标)
{
source.Complete();
target.Completion.Wait(TimeSpan.FromSeconds(1));
}
专用静态缓冲区块设置缓冲区(ITargetBlock块)
{
var buffer=new BufferBlock();
缓冲区链接到(块);
ContinueWith(task=>block.Complete());
返回缓冲区;
}
私人类ActionPerformer
{
专用只读ManualResetEvent resetEvent=新的ManualResetEvent(真);
public List LastReceivedMessage{get;}=new List();
公共void执行(int消息)
{
this.resetEvent.WaitOne(TimeSpan.FromSeconds(3));
this.LastReceivedMessage.Add(消息);
}
公共图书馆
{
此参数为.resetEvent.Reset();
}
公共空间可用()
{
此参数为.resetEvent.Set();
}
}
}
当您将操作块的级别设置为
1
时,这意味着,如果它进行处理,并且队列中已经有项,它将丢弃消息,消息将超出范围。因此,基本上发生的是,您的块执行它的任务,在缓冲区已满时拒绝新消息。之后,广播块完成,整个消息被发送到接收者,它调用
完成
,完成整个管道

var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };
this.broadcastBlock.LinkTo(this.actionBlock, linkOptions);