2017-04-19 59 views
4

考慮下面的代碼爲什麼`Future :: poll`在返回`NotReady`後沒有重複調用?

extern crate futures; 

use std::sync::{atomic, Arc}; 
use futures::*; 

struct F(Arc<atomic::AtomicBool>); 

impl Future for F { 
    type Item =(); 
    type Error =(); 

    fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> { 
     println!("Check if flag is set"); 
     if self.0.load(atomic::Ordering::Relaxed) { 
      Ok(Async::Ready(())) 
     } else { 
      Ok(Async::NotReady) 
     } 
    } 
} 

fn main() { 
    let flag = Arc::new(atomic::AtomicBool::new(false)); 
    let future = F(flag.clone()); 
    ::std::thread::spawn(move || { 
     ::std::thread::sleep_ms(10); 
     println!("set flag"); 
     flag.store(true, atomic::Ordering::Relaxed); 
    }); 
    // ::std::thread::sleep_ms(20); 
    let result = future.wait(); 
    println!("result: {:?}", result); 
} 

的產生的線程設置一個標誌,這對於未來的等待。 我們還會睡眠產生的線程,因此.wait()的初始.poll()調用在標誌設置之前。這會導致.wait()無限期地(看起來)阻塞。如果我們取消註釋thread::sleep_ms.wait()返回,並打印出結果(())。

我期望當前線程多次調用poll來嘗試解決未來問題,因爲我們阻止當前線程。但是,這沒有發生。

我曾嘗試閱讀some docs,而且好像問題是,線程是從poll在第一時間得到NotReadypark版。但是,我不清楚爲什麼這是,或者如何解決這個問題。

我錯過了什麼?

回答

5

爲什麼你需要停下等待的未來,而不是反覆輪詢它?恕我直言,答案相當明顯。因爲在一天結束時它會更快,更高效!

要重複查詢未來(可能被稱爲「忙碌等待」),圖書館將不得不決定是否經常這樣做,否則答案都不會令人滿意。經常這樣做,你會浪費CPU週期,很少做,而且代碼反應遲鈍。

所以是的,當你等待某件事情時,你需要停下任務,然後在你等待的時候停下。就像這樣:

#![allow(deprecated)] 

extern crate futures; 

use std::sync::{Arc, Mutex}; 
use futures::*; 
use futures::task::{park, Task}; 

struct Status { 
    ready: bool, 
    task: Option<Task>, 
} 

#[allow(dead_code)] 
struct F(Arc<Mutex<Status>>); 

impl Future for F { 
    type Item =(); 
    type Error =(); 

    fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> { 
     println!("Check if flag is set"); 
     let mut status = self.0.lock().expect("!lock"); 
     if status.ready { 
      Ok(Async::Ready(())) 
     } else { 
      status.task = Some(park()); 
      Ok(Async::NotReady) 
     } 
    } 
} 

#[test] 
fn test() { 
    let flag = Arc::new(Mutex::new(Status { 
             ready: false, 
             task: None, 
            })); 
    let future = F(flag.clone()); 
    ::std::thread::spawn(move || { 
     ::std::thread::sleep_ms(10); 
     println!("set flag"); 
     let mut status = flag.lock().expect("!lock"); 
     status.ready = true; 
     if let Some(ref task) = status.task { 
      task.unpark() 
     } 
    }); 
    let result = future.wait(); 
    println!("result: {:?}", result); 
} 

注意Future::poll在這裏做的幾件事情:它檢查是否有外部條件,它的停車任務,所以它可能有一個比賽,就像當:

  1. poll檢查變量並且發現它是false;
  2. 外部代碼將變量設置爲true;
  3. 外部代碼檢查任務是否停放並發現它不是;
  4. poll停放任務,但繁榮!現在已經太晚了,沒有人會再放棄它了。

爲了避免任何比賽,我使用了Mutex來同步這些交互。

P.S.如果您只需要將線程結果封裝到Future中,則考慮使用oneshot通道:它具有Receiver,該接口已經實現Future接口。

+2

*答案相當明顯,恕我直言* - 如果答案顯而易見,人們不需要問這個問題^ _^ – Shepmaster

+0

@Shepmaster這是一個有效的jibe,但人們也會問一些問題來確認一些事情或發泄他們的挫折等等。 =) 通過說明答案很明顯,我表示此事很簡單,不需要過時。此外,「小學,我親愛的沃森」:) – ArtemGr

+1

好吧!我瞭解停車計劃背後的動機,但我並沒有意識到我必須手動處理公園材料(儘管這很有意義 - 「期貨」如何知道價值何時準備好?)。謝謝! – MartinHaTh

相關問題