简体   繁体   English

Rx和任务 - 在生成新任务时取消运行任务?

[英]Rx and tasks - cancel running task when new task is spawned?

I have an user interaction scenario I'd like to handle with Rx. 我有一个我想用Rx处理的用户交互场景。

The scenario is similar to the canonical "when user stops typing, do some work" (usually, search for what the user has typed so far) (1) - but I also need to : 该场景类似于规范“当用户停止输入,做一些工作”(通常,搜索用户到目前为止输入的内容)(1) - 但我还需要:

  • (2) only get the latest of the results of "do some work" units (see below) (2)只获得“做一些工作”单位的最新结果(见下文)
  • (3) when a new unit of work starts, cancel any work in progress (in my case it's CPU intensive) (3)当新的工作单元开始时, 取消正在进行的任何工作(在我的情况下,它是CPU密集型的)

For (1) I use an IObservable for the user events, throttled with .Throttle() to only trigger on pauses between events ("user stops typing"). 对于(1)我使用IObservable作为用户事件,使用.Throttle()仅触发事件之间的暂停(“用户停止键入”)。

From that, i .Select(_ => CreateMyTask(...).ToObservable()) . 从那,我。选择.Select(_ => CreateMyTask(...).ToObservable())

This gives me an IObservable<IObservable<T>> where each of the inner observables wraps a single task. 这给了我一个IObservable<IObservable<T>> ,其中每个内部observable包装一个任务。

To get (2) I finally apply .Switch() to only get the results from the newest unit of work. 为了得到(2)我最终应用.Switch()来获得最新工作单元的结果。

What about (3) - cancel pending tasks ? 那么(3) - 取消待定任务?

If I understand correctly, whenever there's a new inner IObservable<T> , the .Switch() method subscribes to it and unsubscribes from the previous one(s), causing them to Dispose() . 如果我理解正确,只要有一个新的内部IObservable<T> .Switch()方法就会订阅它,并取消订阅前一个,导致它们为Dispose()
Maybe that can be somehow wired to trigger the task to cancel? 也许这可能以某种方式连线触发任务取消?

You can just use Observable.FromAsync which will generate tokens that are cancelled when the observer unsubcribes: 您可以使用Observable.FromAsync ,它将生成在观察者取消订阅时取消的标记:

input.Throttle(...)
     .Select(_ => Observable.FromAsync(token => CreateMyTask(..., token)))
     .Switch()
     .Subscribe(...);

This will generate a new token for each unit of work and cancel it every time Switch switches to the new one. 这将为每个工作单元生成一个新令牌,并在每次Switch切换到新工作单时取消它。

Do you have to work with Tasks? 你必须使用任务吗?

If you're happy to work purely with Observables then you can do this nicely yourself. 如果你很乐意与Observables一起工作,那么你可以自己做得很好。

Try doing something like this: 尝试做这样的事情:

var query =
    Observable.Create<int>(o =>
    {
        var cancelling = false;
        var cancel = Disposable.Create(() =>
        {
            cancelling = true;
        });
        var subscription = Observable.Start(() =>
        {
            for (var i = 0; i < 100; i++)
            {
                Thread.Sleep(10); //1000 ms in total
                if (cancelling)
                {
                    Console.WriteLine("Cancelled on {0}", i);
                    return -1;
                }
            }
            Console.WriteLine("Done");
            return 42;
        }).Subscribe(o);
        return new CompositeDisposable(cancel, subscription);
    });

This observable is doing some hard work in the for loop with the Thread.Sleep(10); 这个observable正在使用Thread.Sleep(10);在for循环中做一些艰苦的工作Thread.Sleep(10); , but when the observable is disposed the loop is exited and the intensive CPU work ceases. ,但是当处置了observable时,循环退出并且密集的CPU工作停止。 Then you can use the standard Rx Dispose with the Switch to cancel the in progress work. 然后,您可以使用标准Rx DisposeSwitch取消正在进行的工作。

If you'd like that bundled up in a method, then try this: 如果你想在一个方法中捆绑,那么试试这个:

public static IObservable<T> Start<T>(Func<Func<bool>, T> work)
{
    return Observable.Create<T>(o =>
    {
        var cancelling = false;
        var cancel = Disposable
            .Create(() => cancelling = true);
        var subscription = Observable
            .Start(() => work(() => cancelling))
            .Subscribe(o);
        return new CompositeDisposable(cancel, subscription);
    });
}

And then call it with a function like this: 然后用这样的函数调用它:

Func<Func<bool>, int> work = cancelling =>
{
    for (var i = 0; i < 100; i++)
    {
        Thread.Sleep(10); //1000 ms in total
        if (cancelling())
        {
            Console.WriteLine("Cancelled on {0}", i);
            return -1;
        }
    }
    Console.WriteLine("Done");
    return 42;
};

Here's my code that proved this worked: 这是我的代码证明这有效:

var disposable =
    ObservableEx
        .Start(work)
        .Subscribe(x => Console.WriteLine(x));

Thread.Sleep(500);
disposable.Dispose();

I got "Cancelled on 50" (sometime "Cancelled on 51") as my output. 我得到了“取消50”(有时候“51岁时取消”)作为我的输出。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM