【问题标题】:Preventing a Fn from being invoked again while it's already running防止 Fn 在它已经运行时再次被调用
【发布时间】:2018-07-04 07:22:16
【问题描述】:

我正在使用inputbot 编写一个为我的计算机提供一些全局宏的程序。例如,当我按下 h 键时,它应该执行宏输入

你好世界

进入当前应用程序。我试图这样实现它:

extern crate inputbot;

fn main() {
    let mut callback = || {
        inputbot::KeySequence("Hello World").send();
    };

    inputbot::KeybdKey::HKey.bind(callback);

    inputbot::handle_input_events();
}

然而,当我按下 h 键时,我实际得到的是:

hHHHHHHHHHHHHHHHHHHHHHHHHHEHEHhEhEEHHhEhEhEHhEHHEHHEEHhEHlhEHEHHEHLEHLHeeleleelelelllelelleelehlhehlleeheehelheelleeleelhllllllellelolelellelleoleloloelellololol olollollelllolllol lloo醇○◎升LO lolooloooloo厕所LOWOLO 0 1 OLW WOWO大号WLLOLOW L O,○○○○WOWW低öoOow WWW WOW wowooWWWO oOWRWOoorW¯¯行oOWorororWRRWLR rLROwoRWLWOworo WorrrRWl流öWRLR OLWöOWLDol rollWWLDWowDLlroWWořoWDWOL dorRrwrolrdrrorlrLWDRdodRLowdllrllolrdlrddolrdlrldowldorowlrdlrorloLDLWDLoddlrddlrdldldldrrdordldrlrddrodlrrldoldlrlddldlrdlldlrdlddrlddldddlddlddd P>

宏在每次发送 h 键事件时都会自行触发。 ????

如何防止Fn 在另一个实例仍在运行时再次被调用?这是小型应用程序的主要功能,因此无需担心兼容性问题与。


我天真的尝试修复 这是在main 中添加一个mut running 变量,callback 在运行时将设置为true,或者immediately 在它已经为真时返回:

extern crate inputbot;

use std::time::Duration;
use std::thread::sleep;

fn main() {
    let mut running = false;
    let mut callback = || {
        if running { return };
        running = true;

        inputbot::KeySequence("Hello World").send();

        // wait to make sure keyboard events are done. 
        sleep(Duration::from_millis(125));

        running = false;
    };

    inputbot::KeybdKey::HKey.bind(callback);
    inputbot::handle_input_events();
}

但是,这不会编译:

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnMut`

经过一番阅读,我现在的理解是 Fn 闭包(inputbot 的 .bind() 方法需要)不能拥有任何可变数据,例如捕获的 mut 变量。

也许可以将变量包装在某种非mut 值中?可能是某种锁,以确保潜在的并发安全,比如这个伪代码?

fn main() {
    let mut running = false;
    let lockedRunning = example::Lock(&running);
    let mut callback = || {
        {
            let mut running = lockedRunning.acquire();
            if running { return };
            running = true;
        }

        inputbot::KeySequence("Hello World").send();

        // wait to make sure keyboard events are done. 
        sleep(Duration::from_millis(125));

        {
            let mut running = lockedRunning.acquire();
            running = false;
        }
    };
}

【问题讨论】:

  • 您确定可以保证立即调用回调吗?如果没有,锁可能不是你想要的,因为第一个回调触发的回调可能会在第一个回调返回后调用,所以它可以抓住锁并触发另一轮击键
  • @SvenMarnach 好点,我认为inputbot 没有这样的保证。

标签: concurrency macros rust keyboard-shortcuts


【解决方案1】:

你想要的是这个函数与它自己是互斥的。

Rust 允许您使用 Mutex 结构来执行此操作。它允许你持有一个锁,当你获得它时,它会阻止其他人使用它,直到你释放它。

您想要的具体功能是try_lock 方法,它允许您检查锁是否已被获取并允许您处理这种情况。

let lock = mutex.try_lock();

match lock {
    Ok(_) => {
       // We are the sole owners here
    }
    Err(TryLockError::WouldBlock) => return,
    Err(TryLockError::Poisoned(_)) => {
        println!("The mutex is poisoned");
        return
    }
}

【讨论】:

  • “我们已经毒化了互斥锁”有点模棱两可。互斥锁实际上并没有被try_lock 毒害。
  • RefCell 在这里就足够了吗?我不清楚是否有线程在起作用。
  • 你说得对,可能是这样,我想知道如何才能获得交错而不是无限循环。
  • @MatthieuM。问题指出闭包作为回调工作,库可以从不同的线程调用它。
  • @red75prime:我不知道这个特定的库,所以不知道它是否是多线程的 :)
【解决方案2】:

使用原子值比Mutex 简单一些,因为您无需担心失败情况,并且无需使用惰性静态即可轻松将其制成静态变量:

use std::sync::atomic::{AtomicBool, Ordering};

fn main() {
    let is_being_called = AtomicBool::new(false);

    bind(move || {
        if !is_being_called.compare_and_swap(false, true, Ordering::SeqCst) {
            print!("I'm doing work");
            is_being_called.store(false, Ordering::SeqCst);
        }
    });
}

我有一种预感,这也比使用 Mutex 更有效,因为不需要进行堆分配,但我没有对其进行基准测试。

如果您在单线程上下文中并且您的回调以某种方式(意外?)递归(which closures cannot be),您也可以使用Cell

use std::cell::Cell;

fn main() {
    let is_being_called = Cell::new(false);

    bind(move || {
        if !is_being_called.get() {
            is_being_called.set(true);
            print!("doing work");
            is_being_called.set(false);
        }
    })
}

如果你碰巧有一个 FnMut 闭包,你甚至不需要 Cell 并且可以只使用一个布尔值:

fn main() {
    let mut is_being_called = false;

    bind(move || {
        if !is_being_called {
            is_being_called = true;
            print!("doing work");
            is_being_called = false;
        }
    })
}

【讨论】:

  • 这怎么比try_lock简单?我看到的主要区别是你把中毒变成了僵局。
  • @CodesInChaos 你有什么建议我可以改进我的句子吗?我陈述了我说得更简单的理由:“因为你不需要担心某些失败案例,它可以很容易地变成静态变量”?我也错过了你看到僵局的地方......你能再扩展一下吗?
  • 哪些故障情况不用担心?我能想到的唯一失败情况是锁内的代码是否出现恐慌,在这种情况下,解包 try_lock 会因中毒互斥恐慌(易于调试)而崩溃,而您的代码在下次尝试获取锁时会永远挂起。跨度>
  • @CodesInChaos 实际上是在第一个代码示例中没有任何Result ——它不能失败。为什么会永远挂着?如果执行带有println!("recur"); 的块,则原子变量已设置为true;以后所有读取该变量的尝试都将返回true,并且该函数不会执行。我不确定您所说的“获取锁”是什么意思; AFAIK 这不是原子变量的工作方式。
  • 我误读为while。但是if 仍然有类似的效果,静默阻止这段代码在恐慌后再次运行。 (获取锁是指你的CAS指令,对应一个简单自旋锁的try_lock操作)
猜你喜欢
  • 2012-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
  • 2022-11-11
  • 2012-05-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多