如何关闭已修改并正在执行的“ futures :: sync :: mpsc :: Receiver”流?

时间:2018-12-23 16:42:20

标签: rust rust-tokio

我希望能够按照以下步骤做一些事情,以便异步关闭Receiver流:

extern crate futures;
extern crate tokio;

use futures::future::lazy;
use futures::stream::AndThen;
use futures::sync::mpsc::Receiver;
use futures::{Future, Sink, Stream};
use std::sync::{Arc, Mutex};

use tokio::timer::{Delay, Interval};

fn main() {
    tokio::run(lazy(|| {
        let (tx, rx) = futures::sync::mpsc::channel(1000);

        let arc = Arc::new(Mutex::<Option<AndThen<Receiver<u32>, _, _>>>::new(None));

        {
            let mut and_then = arc.lock().unwrap();
            *and_then = Some(rx.and_then(|num| {
                println!("{}", num);
                Ok(())
            }));
        }

        let arc_clone = arc.clone();
        // This is the part I'd like to be able to do
        // After one second, close the `Receiver` so that future
        // calls to the `Sender` don't call the callback above in the
        // closure passed to `rx.and_then`
        tokio::spawn(
            Delay::new(std::time::Instant::now() + std::time::Duration::from_secs(1))
                .map_err(|e| eprintln!("Some delay err {:?}", e))
                .and_then(move |_| {
                    let mut maybe_stream = arc_clone.lock().unwrap();
                    match maybe_stream.take() {
                        Some(stream) => stream.into_inner().close(),
                        None => eprintln!("Can't close non-existent stream"), // line "A"
                    }
                    Ok(())
                }),
        );

        {
            let mut maybe_stream = arc.lock().unwrap();
            let stream = maybe_stream.take().expect("Stream already ripped out"); // line "B"

            let rx = stream.for_each(|_| Ok(()));
            tokio::spawn(rx);
        }

        tokio::spawn(
            Interval::new_interval(std::time::Duration::from_millis(10))
                .take(10)
                .map_err(|e| {
                    eprintln!("Interval error?! {:?}", e);
                })
                .fold((tx, 0), |(tx, i), _| {
                    tx.send(i as u32)
                        .map_err(|e| eprintln!("Send error?! {:?}", e))
                        .map(move |tx| (tx, i + 1))
                })
                .map(|_| ()),
        );

        Ok(())
    }));
}

Playground

但是,行A运行是因为我必须在行B上移动流才能在其上调用.for_each。如果我不打.for_each(或类似的电话),就我所知,我根本无法执行AndThen。我不能在不实际移动对象的情况下调用.for_each,因为for_each是一种移动方法。

我可以做我想做的事吗?看来这绝对应该是可能的,但也许我缺少明显的东西。

我在使用0.1的期货,在0.1的东京。

2 个答案:

答案 0 :(得分:2)

不要说谎,我和@shepmaster在一起,您的问题还不清楚。就是说,感觉就像您正在尝试做mpsc的{​​{1}}部分不适合做的事情。

无论如何。解释时间。

每当您组合/组合流(或期货!)时,每种组合方法都采用futures,而不是我想您希望的self&self

进入您的代码块的那一刻:

&mut self

...当您 { let mut maybe_stream = arc.lock().unwrap(); let stream = maybe_stream.take().expect("Stream already ripped out"); // line "B" let rx = stream.for_each(|_| Ok(())); tokio::spawn(rx); } Arc<Option<Receiver<T>>>中提取流时,其内容被take()替换。然后,将其生成到Tokio反应堆上,该反应堆开始处理此部分。该None现在处于循环状态,不再对您可用。此外,您的rx现在包含maybe_stream

在延迟之后,您然后尝试None的内容take()(A行)。由于现在什么都没有了,因此您一无所有,因此也没有什么可以关闭的了。您的代码错误了。

使用一种机制来停止流本身,而不是绕过Arc<Option<Receiver<T>>>并希望销毁它。您可以自己这样做,也可以使用mpsc::Receiver之类的板条箱为您这样做。

DIY版本在这里,可通过您的代码进行修改:

stream-cancel

Playground

添加的extern crate futures; extern crate tokio; use futures::future::lazy; use futures::{future, Future, Sink, Stream}; use std::sync::{Arc, RwLock}; use std::sync::atomic::{Ordering, AtomicBool}; use tokio::timer::{Delay, Interval}; fn main() { tokio::run(lazy(|| { let (tx, rx) = futures::sync::mpsc::channel(1000); let circuit_breaker:Arc<AtomicBool> = Arc::new(AtomicBool::new(false)); let c_b_copy = Arc::clone(&circuit_breaker); tokio::spawn( Delay::new(std::time::Instant::now() + std::time::Duration::from_secs(1)) .map_err(|e| eprintln!("Some delay err {:?}", e)) .and_then(move |_| { // We set the CB to true in order to stop processing of the stream circuit_breaker.store(true, Ordering::Relaxed); Ok(()) }), ); { let rx2 = rx.for_each(|e| { println!("{:?}", e); Ok(()) }); tokio::spawn(rx2); } tokio::spawn( Interval::new_interval(std::time::Duration::from_millis(100)) .take(100) // take_while causes the stream to continue as long as its argument returns a future resolving to true. // In this case, we're checking every time if the circuit-breaker we've introduced is false .take_while(move |_| { future::ok( c_b_copy.load(Ordering::Relaxed) == false ); }) .map_err(|e| { eprintln!("Interval error?! {:?}", e); }) .fold((tx, 0), |(tx, i), _| { tx.send(i as u32) .map_err(|e| eprintln!("Send error?! {:?}", e)) .map(move |tx| (tx, i + 1)) }) .map(|_| ()), ); Ok(()) })); } 允许您对流的内容或外部谓词进行操作,以继续或停止流。请注意,即使我们使用的是take_while(),由于Tokio的AtomicBool生命周期要求,我们仍然需要Arc

逆流

在评论中进行一些讨论之后,this solution可能更适合您的用例。我有效地实现了断路器覆盖的扇出流。魔术发生在这里:

'static

如果状态指示器设置为false,则对上面的流进行轮询;然后将结果发送给所有侦听器。如果impl<S> Stream for FanOut<S> where S:Stream, S::Item:Clone { type Item = S::Item; type Error = S::Error; fn poll(&mut self) -> Result<Async<Option<S::Item>>, S::Error> { match self.inner.as_mut() { Some(ref mut r) => { let mut breaker = self.breaker.write().expect("Poisoned lock"); match breaker.status { false => { let item = r.poll(); match &item { &Ok(Async::Ready(Some(ref i))) => { breaker.registry.iter_mut().for_each(|sender| { sender.try_send(i.clone()).expect("Dead channel"); }); item }, _ => item } }, true => Ok(Async::Ready(None)) } } _ => { let mut breaker = self.breaker.write().expect("Poisoned lock"); // Stream is over, drop all the senders breaker.registry = vec![]; Ok(Async::Ready(None)) } } } } 的结果为poll(表明流已完成),则关闭所有侦听器通道。

如果状态指示器设置为true,则关闭所有侦听器通道,并且流返回Async::Ready(None)(并由Tokio丢弃)。

Async::Ready(None)对象是可克隆的,但是只有初始实例才能执行任何操作。

答案 1 :(得分:1)

您可以使用stream-cancel之类的板条箱来实现此目的。在这里,我使用了Valved流包装器,该包装器获取现有流并返回一个值,以后可用来取消该流:

use futures::{
    future::lazy,
    {Future, Sink, Stream},
}; // 0.1.25
use stream_cancel::Valved; // 0.4.4
use tokio::timer::{Delay, Interval}; // 0.1.13

fn main() {
    tokio::run(lazy(|| {
        let (tx, rx) = futures::sync::mpsc::channel(1000);
        let (trigger, rx) = Valved::new(rx);

        tokio::spawn({
            rx.for_each(|num| {
                println!("{}", num);
                Ok(())
            })
        });

        tokio::spawn({
            Delay::new(std::time::Instant::now() + std::time::Duration::from_secs(1))
                .map_err(|e| eprintln!("Some delay err {:?}", e))
                .map(move |_| trigger.cancel()),
        });

        tokio::spawn({
            Interval::new_interval(std::time::Duration::from_millis(10))
                .take(10)
                .map_err(|e| eprintln!("Interval error?! {:?}", e))
                .fold((tx, 0), |(tx, i), _| {
                    tx.send(i)
                        .map_err(|e| eprintln!("Send error?! {:?}", e))
                        .map(move |tx| (tx, i + 1))
                })
                .map(|_| ()),
        });

        Ok(())
    }));
}

板条箱还具有其他类型,可用于不同的用例,请务必查看文档。

请参阅Sébastien Renauld's answer,了解一种自己实现此方法的方法。