You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在发生事件B时取消已防抖的Rx事件A?

需求:用事件B取消防抖中的事件A

我需要实现这样的逻辑:当事件B发生时,取消正在防抖处理的事件A。比如,若期间点击了鼠标按钮,就忽略已防抖的键盘按键事件。

我用计时器延迟模拟事件A和B,尝试用Rx.NET的Throttle操作符对A进行防抖,但结果不符合预期:

var subjA = new Subject<int>();
var subjB = new Subject<Unit>();

// 期望输出:3(因为B在时间线150ms时触发)
// 实际输出:2, 3

subjA
    .Throttle(TimeSpan.FromMilliseconds(200))
    .Subscribe(s => Console.WriteLine(s)); 

await Task.WhenAll(EmitA(), EmitB(), Task.Delay(2000));

async Task EmitA()
{
    subjA!.OnNext(1);
    await Task.Delay(100);
    subjA!.OnNext(2);
    await Task.Delay(500);
    subjA!.OnNext(3);
}

async Task EmitB()
{
    await Task.Delay(150);
    subjB!.OnNext(Unit.Default);
}

我已经通过Select/Delay/TakeUntil/Switch的组合解决了问题,但感觉这个写法对于Rx的常见场景来说过于复杂:

#nullable enable
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using System;

var subjA = new Subject<int>();
var subjB = new Subject<Unit>();

subjA.Select(n =>
        Observable.Return(n).Delay(TimeSpan.FromMilliseconds(200))
        .TakeUntil(subjB))
    .Switch()
    .Subscribe(s => Console.WriteLine(s));

await Task.WhenAll(EmitA(), EmitB(), Task.Delay(2000));

async Task EmitA()
{
    subjA!.OnNext(1);
    await Task.Delay(100);
    subjA!.OnNext(2);
    await Task.Delay(500);
    subjA!.OnNext(3);
}

async Task EmitB()
{
    await Task.Delay(150);
    subjB!.OnNext(Unit.Default);
}

有没有更优雅的实现方式?


大理石图:

subjA:  +---1---2---------------3---------|
subjB:  +----------U-------------------------------|
Result: +-----------------------------3---|

更优雅的实现方式

你可以用Observable.Timer替代Return+Delay,让代码更简洁,核心逻辑和你的方案一致,但写法更紧凑:

subjA
    .Select(n => Observable.Timer(TimeSpan.FromMilliseconds(200))
        .Select(_ => n)
        .TakeUntil(subjB))
    .Switch()
    .Subscribe(Console.WriteLine);

如果这类场景在你的代码中频繁出现,还可以封装成自定义操作符,进一步提升复用性和代码可读性:

public static class ObservableExtensions
{
    public static IObservable<T> ThrottleUntil<T, TInterrupt>(
        this IObservable<T> source,
        TimeSpan dueTime,
        IObservable<TInterrupt> interruptSignal)
    {
        return source
            .Select(item => Observable.Timer(dueTime)
                .Select(_ => item)
                .TakeUntil(interruptSignal))
            .Switch();
    }
}

使用时就非常简洁:

subjA
    .ThrottleUntil(TimeSpan.FromMilliseconds(200), subjB)
    .Subscribe(Console.WriteLine);

这个自定义操作符清晰表达了“防抖直到中断信号触发”的逻辑,完全贴合你的需求,同时保持了Rx代码的声明式风格。


内容的提问来源于stack exchange,提问作者noseratio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 01:46:47