延迟合并处理任务

我们经常会遇到一种情况,在带有智能提示的文本框,在输入内容时,会实时或准实时弹出提示下拉框,里面包含系统猜测你可能要输入的内容。当这些搜索建议来自服务器的时候,有时你会觉得这种智能提示对服务器的负载有点大,毕竟但用户输入完一定内容之前,会产生多余的运算和流量,在此过程中所产生的结果甚至用户根本不会留意,因此还不如忽略掉这期间的过程。

那么,如何做到这一点呢?

延迟合并处理任务应运而生。其实,不光前面所说的这个场景,还有许多情况也会需要应用到这种类型的任务。该任务满足一下几点功能条件。

  • 可以创建一个任务方法,里面会包含注册的业务方法。任务方法用于在需要延迟调用业务方法的地方调用,后续的延迟与合并操作会由任务方法自动处理。
  • 在任务方法执行时,会在延迟一段时间后才执行所注册的业务方法。
  • 任务方法可以在不同地方分别调用多次。
  • 当上一个任务方法的延迟时限还没到时,如果下一个任务方法被调用,那么上一个任务方法会被终止,也即此时只会在最后一个任务方法中经过延迟一段时间后调用业务方法。此乃置后合并是也,用于过滤掉前面的多余执行。

为此,我们可以设计一个通用的方式,来解决这些问题。我们需要新写一个类,里面需要提供一个时长设置,即两次触发之间的最长间隔,短语该间隔的两次触发将会被自动合并;还需要一个事件,用于在触发真正被认定为需要执行时,会抛该事件,业务逻辑可以绑定在该事件上;最后,还需要一个异步方法,即用于尝试执行触发,其应当返回最终是否真实触发。

 1 public class DelayTask
 2 {
 3     public TimeSpan Span { get; set; }
 4  
 5     public event EventHandler Processed;
 6  
 7     public async Task<bool> Process()
 8     {
 9         throw new NotImplementedException();
10     }
11  
12 }

当然,在此之前,其实我们应该先引用一些命名空间。

1 using System;
2 using System.Threading;
3 using System.Threading.Tasks;

好了,我们继续实现该类。在 Process 成员方法中,我们计划延迟执行预期操作。然而,对于正在处理的过程中,如果有新的触发进来,则我们应当将之前触发摒弃掉。一个简单的做法是,之前触发在等待后,进行一次校验。这时,我们需要通过一个标识符来作为判断的依据。每次新的触发进来时,需要重置该标识符,并记录下来,并在等待一段时间后,用记录下来的标识符和当前最新的进行比较,如果相等,则说明没有新的触发进来,否则直接停止执行。

 1 private Guid _token = Guid.Empty;
 2 
 3 public async Task<bool> Process()
 4 {
 5     var token = Guid.NewGuid();
 6     _token = token;
 7     await Task.Delay(Span);
 8     if (token != _token) return false;
 9     _token = Guid.Empty;
10     Processed(this, new EventArgs());
11     return true;
12 }

当然,也可以用 CancellationToken 来提前终止,此处就不做此优化了。但对于 Process 本身,我们将加入该取消机制。于是我们得到最终代码。

 1 /// <summary>
 2 /// The delay task.
 3 /// </summary>
 4 public class DelayTask
 5 {
 6     private Guid _token = Guid.Empty;
 7  
 8     /// <summary>
 9     /// Gets or sets the delay time span.
10     /// </summary>
11     public TimeSpan Span { get; set; }
12  
13     /// <summary>
14     /// Adds or removes the event handler occurred
15     /// after processed.
16     /// </summary>
17     public event EventHandler Processed;
18  
19     /// <summary>
20     /// Processes the delay task.
21     /// </summary>
22     /// <returns>
23     /// A task with a value indicating whether it executes.
24     /// </returns>
25     public async Task<bool> Process()
26     {
27         return await Process(CancellationToken.None);
28     }
29  
30     /// <summary>
31     /// Processes the delay task.
32     /// </summary>
33     /// <param name="cancellationToken">
34     /// The cancellation token that will be checked prior
35     /// to completing the returned task.
36     /// </param>
37     /// <returns>
38     /// A task with a value indicating whether it executes.
39     /// </returns>
40     public async Task<bool> Process(
41         CancellationToken cancellationToken)
42     {
43         var token = Guid.NewGuid();
44         _token = token;
45         await Task.Delay(Span, cancellationToken);
46         if (token != _token) return false;
47         _token = Guid.Empty;
48         if (cancellationToken.IsCancellationRequested)
49             return false;
50         Processed(this, new EventArgs());
51         return true;
52     }
53 }

这个类的使用也非常方便,只需初始化该对象,然后设置好最长间隔时长,并将原先的业务逻辑绑定到其事件上,然后在原本应当调用业务逻辑的地方,调用其 Process 方法即可。

【完】

文章类型及复杂度:C# 和 .Net 进阶。

节选翻译自 MSDN 博文 Delay Task,内容有所调整。

http://blogs.msdn.com/b/kingcean/archive/2016/03/20/delay-task.aspx

posted @ 2016-07-05 15:21  Kingcean  阅读(472)  评论(0编辑  收藏  举报