在rx中去抖动的最佳方法是什么?

时间:2012-02-24 11:44:02

标签: task-parallel-library system.reactive

我正在使用rx观察事件。 只有一个用户进行了长时间的操作。

当前一操作未完成时,事件可能会再次触发。 如何放弃这个事件?

以下是我正在搜索的内容:类似于SubscribeWithDebounceAsync方法:

var observable = Observable.FromEventPattern<T>(obj, "OnSomeEvent");
observable.SubscribeWithDebounceAsync( ep => .... );

2 个答案:

答案 0 :(得分:0)

您的问题听起来好像您希望在引发事件时执行此操作并且您当前没有执行该操作。您的评论建议您只想执行一次操作。

后者更简单:

var observable = Observable.FromEventPattern<T>(...);
var disp = observable.Take(1).Subscribe(ep => ...);

对于前者,我将假设您打算在另一个线程上执行此长时间运行的操作。如果不这样做,您将保持Observable正在引发消息的线程。 SerialDisposable是你的朋友:

<Extension()>
Public Function IgnoreWhileExecuting(Of T)(source As IObservable(Of T),
                                           onNext As Func(Of T, Task),
                                           onError As Action(Of Exception),
                                           onCompleted As Action
                                          ) As IDisposable
    'argument validation/error handling skipped for sample
    Dim serial As New SerialDisposable

    Dim syncNext As Action(Of T) = Nothing
    'this function will be called for the initial and subsequent subscriptions
    Dim subscribe As Func(Of IDisposable) =
        Function()
            Return source.Subscribe(syncNext,
                                    onError,
                                    onCompleted)
        End Function
    'this function will be "suspend" the subscription while executing
    'the long-running operation, but it must return immediately to 
    syncNext = Sub(v)
                   'stop the current subscription to the source
                   serial.Disposable.Dispose()
                   'perform the long running operation and follow
                   'with resubscription.  This will resubscribe regardless
                   'of if the task completes successfully or not
                   onNext(v).ContinueWith(Sub() serial.Disposable = subscribe())
               End Sub
    serial.Disposable = subscribe()
    Return serial
End Function

由于这将订阅和取消订阅,因此通常假设您有一个热门观察,就像您在问题中使用的可观察事件一样。您可以在控制台应用程序中对此进行测试。

Sub Main()
    Dim left = Observable.Interval(TimeSpan.FromMilliseconds(500))
    Dim leftHot = left.Do(Sub(v) WriteTimestamped("Tick {0}", v)).Publish()
    Dim f As New TaskFactory
    Dim disp = leftHot _
                   .IgnoreWhileExecuting(Function(v)
                                             Return f.StartNew(Sub(tparam)
                                                                   WriteTimestamped("Before sleep {0}", tparam)
                                                                   Thread.Sleep(2000)
                                                                   WriteTimestamped("After sleep {0}", tparam)
                                                               End Sub,
                                                               v)
                                         End Function,
                                         Sub(ex) Console.WriteLine("Error in ignore: " & ex.ToString()),
                                         Sub() Console.WriteLine("Completed from ignore"))
    Dim con = leftHot.Connect()
    Console.ReadKey()
    disp.Dispose()
    Console.ReadKey()
    con.Dispose()
    Console.ReadKey()
End Sub

Private Sub WriteTimestamped(ByVal format As String, ByVal arg As Object)
    Console.WriteLine(Date.Now.ToString("HH:mm:ss.f") & " " & String.Format(format, arg))
End Sub

要查看热和冷之间的区别,请移除Publish调用和相应的Connect以及连接处理,然后重新运行该示例。

答案 1 :(得分:0)

这是一个不同的实现:

public static class Extensions
{
    public static IDisposable SubscribeWithDebounceAsync<T>(
      this IObservable<T> source, 
      Action<T> longRunningTask)
    {
        var finished = new Subject<Unit>();
        var debounced = source
          .SkipUntil(finished)
          .Take(1)
          .Repeat()
          .Subscribe(
              t => Observable.Start(() => 
                {
                  longRunningTask(t);
                  finished.OnNext(Unit.Default);
                }));
        finished.OnNext(Unit.Default);
        return debounced;
    }
}

最后一次完成.OnNext是为了让进程开始,否则它将永远停在SkipUntil上(已完成)。

编辑:使代码更简洁。