C++ 函数消抖

函数消抖 指在短时间内连续多次调用同一函数,仅最后一次调用生效。

形如:

1auto debouncedFn = debounce(fn, 100);

通常将需要消抖的函数封装成一个新的函数,新的函数进行延迟后调用原函数:

  • 如果在延时结束前再次调用,则重新开始延时
  • 如果在延时结束前没有再次调用,则使调用原函数

示例

这个实现存在一个问题,那就是通过 thread.join(); 等待上一次调用取消,会产生阻塞,不能用于实际开发。

1#include <functional>
2#include <chrono>
3#include <thread>
4#include <mutex>
5#include <future>
6#include <memory>
7#include <exception>
8
9namespace Utils
10{
11
12template<typename R, typename... ARGS>
13std::enable_if_t<!std::is_void_v<R> && (!std::is_void_v<ARGS> && ...), void>
14invoke(std::shared_ptr<std::promise<R>> promise, std::function<R(ARGS...)> fn, ARGS... args)
15{
16    promise->set_value(fn(args...));
17}
18
19template<typename R>
20void invoke(std::shared_ptr<std::promise<R>> promise, std::function<R(void)> fn)
21{
22    promise->set_value(fn());
23}
24
25template<typename... ARGS>
26void invoke(std::shared_ptr<std::promise<void>> promise, std::function<void(ARGS...)> fn, ARGS... args)
27{
28    fn(args...);
29    promise->set_value();
30}
31
32void invoke(std::shared_ptr<std::promise<void>> promise, std::function<void(void)> fn)
33{
34    fn();
35    promise->set_value();
36}
37
38/*************************************************************************
39 * @brief 函数消抖
40 * @param fn 希望消抖函数
41 * @param ms 消抖的延时时间,毫秒
42 * @return 一个封装了消抖功能的函数
43 *************************************************************************/
44template<typename R, typename... ARGS>
45std::function<std::shared_ptr<std::promise<R>>(ARGS...)> debounce(std::function<R(ARGS...)> fn, int ms)
46{
47    auto lambda = [fn = std::move(fn), ms](ARGS... args) mutable -> std::shared_ptr<std::promise<R>> {
48        std::shared_ptr<std::promise<R>> promise = std::make_shared<std::promise<R>>();
49        static auto lastCallTime = std::chrono::steady_clock::now();
50        static bool cancel = false;
51        static std::thread thread;
52        static std::mutex mutex;
53
54        if (thread.joinable())
55        {
56            mutex.lock();
57            cancel = true;
58            mutex.unlock();
59
60            thread.join();
61
62            mutex.lock();
63            cancel = false;
64            mutex.unlock();
65        }
66
67        thread = std::thread([fn, ms, promise, args...]() mutable {
68            std::this_thread::sleep_until(lastCallTime + std::chrono::milliseconds(ms));
69
70            mutex.lock();
71            if (cancel)
72            {
73                mutex.unlock();
74                promise->set_exception(std::make_exception_ptr(std::runtime_error("cancel")));
75                return;
76            }
77            mutex.unlock();
78
79            // promise->set_value(fn(args...));
80            invoke(promise, fn, args...);
81            thread.detach();
82        });
83
84        return promise;
85    };
86
87    return lambda;
88}
89
90template<typename R, typename... ARGS>
91std::function<std::shared_ptr<std::promise<R>>(ARGS...)> debounce(R(*fn)(ARGS...), int ms)
92{
93    return debounce(static_cast<std::function<R(ARGS...)>>(fn), ms);
94}
95
96}; // namespace Utils

测试

1int main()
2{
3    auto fn = Utils::debounce(test, 1000);
4    auto p1 = fn(1, 2);
5    auto p2 = fn(3, 4);
6
7    auto f1 = p1->get_future();
8    auto f2 = p2->get_future();
9
10    f1.wait();
11    f2.wait();
12
13    try {
14        if (f1.valid())
15            printf("%d\n", f1.get());
16    } catch (std::runtime_error e) {
17        printf("%s\n", e.what());
18    }
19
20    try {
21        if (f2.valid())
22            printf("%d\n", f2.get());
23    } catch (std::runtime_error e) {
24        printf("%s\n", e.what());
25    }
26
27    return 0;
28}

结果:

1test 3 4
2cancel
37