WPF开发中的防抖和节流

前言

假如我们有一个上线用户的界面,每个用户上线的时候都会做一定的业务处理和页面刷新。

这样如果在短时间内用户大量进入就会导致页面处理不过来,而产生未响应甚至闪退。

这就要用到防抖和节流了。

它们都用在事件频繁触发并且允许丢失的场景下。

防抖和节流都是为了防止事件频发触发的一种方式。

函数防抖(debounce)

当持续触发某事件时,一定时间间隔内没有再触发事件时,事件处理函数才会执行一次,如果设定的时间间隔到来之前,又一次触发了事件,就重新开始延时。

可以保证最后的事件一定触发,但是可能中间可能新事件一直有,则一直取消触发。

函数节流(throttle)

当持续触发事件时,有规律的每隔一个时间间隔执行一次事件处理函数。

可以保证事件流中稳定的触发事件,但是不能保证最后的事件一定被触发。

函数防抖(debounce)

针对用户上线的场景适用防抖更加合适。

在 C# 页面渲染中,防抖处理通常用于限制用户频繁触发某个操作(如按钮点击、输入框输入等)时的请求或处理次数,以提高系统性能和用户体验。

不带参数

页面内的渲染方法基本上都不是静态的,并且参数可以使用页面的私有参数,方法本身不用传参数。

页面中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private readonly Action _renderPage;
public AttendanceWindow()
{
//页面初始化时初始化防抖对象
_renderPage = new ZDebounceUtil().Create(RenderUserList, TimeSpan.FromSeconds(0.5));
}

private void RenderUserList()
{
Dispatcher.Invoke(
() =>
{
//UI渲染
}
);
}

//频繁触发的方法
public void UserListChange()
{
//原来调用RenderUserList()改成如下
_renderPage?.Invoke();
}

工具类

ZDebounceUtil.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Threading;

public class ZDebounceUtil
{
private Timer _timer;
private Action _action;
private TimeSpan _timeout;

private void DebouncedAction()
{
_timer?.Dispose();
_timer = new Timer(
state =>
{
_action();
},
null,
_timeout,
TimeSpan.FromMilliseconds(-1)
);
}

public Action Create(Action action, TimeSpan timeout)
{
_action = action;
_timeout = timeout;
return DebouncedAction;
}
}

带参数示例

下面是一个示例代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void Main(string[] args)
{
Action<string> renderPage = new Debounce<string>().Create(RenderUserList, TimeSpan.FromSeconds(0.5));

// 模拟用户频繁触发页面渲染的情况
for (int i = 1; i <= 10; i++)
{
renderPage.Invoke($"Page {i}");
}

// 防抖处理后,只会执行最后一次调用
// 输出:Page 10
}

static void RenderUserList(string pageName)
{
Console.WriteLine($"Rendering {pageName}...");
// 实际的页面渲染逻辑
}

防抖工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System;
using System.Threading;

public class Debounce<T>
{
private Timer _timer;
private Action<T> _action;

public Action<T> Create(Action<T> action, TimeSpan timeout)
{
_action = action;
return DebouncedAction;
void DebouncedAction(T param)
{
_timer?.Dispose();
_timer = new Timer(
state =>
{
_action(param);
},
null,
timeout,
TimeSpan.FromMilliseconds(-1)
);
}
}
}

在此示例中,我们定义了一个名为 Debounce<T> 的通用类。

通过调用 Create 方法,我们可以创建一个经过防抖处理的函数。

当调用 renderPage.Invoke(pageName) 时,会在指定的时间间隔内只执行最后一次调用。

Render 方法中,我们模拟了实际的页面渲染逻辑,你可以根据实际情况进行修改。

函数节流(throttle)

不带参数

页面中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private readonly Action _renderPage;
public AttendanceWindow()
{
//页面初始化时初始化节流对象
_renderPage = new ZThrottleUtil().Create(RenderUserList, TimeSpan.FromSeconds(0.5));
}

private void RenderUserList()
{
Dispatcher.Invoke(
() =>
{
//UI渲染
}
);
}

//频繁触发的方法
public void UserListChange()
{
//原来调用RenderUserList()改成如下
_renderPage.Invoke();
}

工具类

ZThrottleUtil.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Threading;

public class ZThrottleUtil {
private Timer _timer;
private Action _action;
private TimeSpan _timeout;

private void ThrottleAction()
{
if (_timer != null)
{
return;
}
_timer = new Timer(
state =>
{
_action();
_timer?.Dispose();
_timer = null;
},
null,
_timeout,
TimeSpan.FromMilliseconds(-1)
);
}

public Action Create(Action action, TimeSpan timeout)
{
_action = action;
_timeout = timeout;
return ThrottleAction;
}
}