One kind of minimalist design and implementation of asynchronous time-out mechanism (C # version) treatment

1 Introduction

After performing some action, we will look forward to feedback. The final result either been, or is timed out. When a timeout occurs, it may be desirable to be informed, or wish to automatically retry, and the like. Thus the design of a universal asynchronous timeout handling mechanism, in order to complete the processing of time-out by simple and easy to understand manner.

2. External Interface Design

From the perspective of use, it is desirable that the caller "specify the timeout duration, the time to automatically execute a specified process", which can be drawn outside of the operating parameters of the interface. From a functional point of view, the case has not expired, it is necessary to provide clearing time out tasks at any time in the timeout period features.

2.1 user interface

Here, we have said in mechanism design "timeout task runner" Seen from the outside, its interface and functionality is structured as follows:
(1) adding a timeout task, to bring callback object identification for mass participation, length of time specified timeout and a timeout callback method to handle the timeout to timeout task runner. Returns a task ID, delete the timeout for subsequent tasks.
(2) delete the task timeout, specify the task ID to delete. At the same time, support for all tasks to clear a timeout object.

2.2 use

Initiate an asynchronous operation at the same time, add timeout task, upon successful asynchronous operation, delete the timeout task. Timeout run automatically perform the task timeout. Below, in part by the gray runner:

3. Design and implement a timeout task runner

First, the length of the second set the precise size, which represents the lowest supporting-second timeout (nonsense). The basic idea designed for: the charged time-out task, runner create a list, and in seconds on the list of tasks to detect for the time has reached or exceeded the execution queue move it to time out task, by an independent timeout task running thread to perform the task queue. Here, to move a task execution queue detection Called the "producer", the task execution thread called "consumers."

3.1 The basic structure

Run maintains a list of tasks and a timeout execution queue, by a timeout detection, which uses the timer to detect whether the task execution queue timeout and add overtime, a task executor is responsible for running the callback timeout task.

3.2 Data Structure

Timeout task information, in addition to the caller identification object passed, and long timeout callback method, further comprising the desired operation during other attributes: job identifier, the operating point of time. For more information see the class definition as follows:

    /// <summary>
    /// 超时回调的委托
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="objectKey"></param>
    public delegate void TimeoutCallback<T>(T objectKey);

    /// <summary>
    /// 超时任务信息
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class TimeoutTask<T>
    {
        // 任务标识(由超时任务运行器自动分配)
        public long TaskId { get; set; }
        // 对象标识
        public T ObjectKey { get; set; }
        // 超时秒数
        public int TimeoutSeconds { get; set; }
        /// <summary>
        /// 以秒为单位的 Tick 值,由超时任务运行器根据当前时间加上超时秒数计算设置
        /// DateTime.Ticks 是以 10ns(10纳秒) 为单位
        /// 将其除以 10 单位为 ws(微秒),再除以 1000 为 ms(毫秒),再除以 1000 为 s(秒)
        /// 累计为 DateTime.Ticks / 10000000
        /// </summary>
        public long ExecuteSecondTicks { get; set; }
        // 超时回调方法
        public TimeoutCallback<T> Callback { get; set; }
    }

3.3 Timeout Task List

任务清单,在操作粒度上,可以以任务标识为单位,也可以以对象标识为单位,因此,为了快速检索。任务清单分两种形式存储,一种以任务标识为主键,另一种以对象标识为主键,其结构如下:

具体类型结构定义如下,_DictionaryLocker 有于同步加锁,确保线程安全。

    // 以 TaskId(任务标识) 为 KEY 的任务清单字典
    private Dictionary<long, TimeoutTask<T>> _TaskIdDictionary = new Dictionary<long, TimeoutTask<T>>();
    // 以 ObjectId(任务相关对象标识) 为 KEY 的任务字典,因每个对象可以有多个超时任务,所以为列表
    private Dictionary<T, List<TimeoutTask<T>>> _TaskObjectKeyDictionary = new Dictionary<T, List<TimeoutTask<T>>>();
    // 用于同步操作上述两个清单字典,使得线程安全
    private object _DictionaryLocker = new object(); 

3.4任务执行队列

一个普通的先进先出的队列,_RunLocker 用于线程安全加锁。

    // 已超时任务队列,由任务运行线程逐个执行
    private Queue<TimeoutTask<T>> _TaskRunQueue = new Queue<TimeoutTask<T>>();
    // 用来同步操作任务队列,使得线程安全(生产者,消费者模式)
    private object _RunLocker = new object();

3.5超时检测者

以每秒进行一次检测的粒度运行,使用 System.Timers.Timer 非常合适,它的职能是判断运行时间到达与否决定是否将任务移至执行队列。

    // 超时检测者,每秒扫描是否达到超时,超时则加入超时任务队列
    private System.Timers.Timer _TimeoutChecker = new System.Timers.Timer();

    // 超时检测者
    _TimeoutChecker.Interval = 1000;
    _TimeoutChecker.Elapsed += new System.Timers.ElapsedEventHandler(CheckTimerTick);
    _TimeoutChecker.Start();

    /// <summary>
    /// 超时任务检测者
    /// 对于,时间已经超过了设定的超时时间的,加入超时任务执行队列
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void CheckTimerTick(object sender, System.Timers.ElapsedEventArgs e)
    {
        long secondTicks = DateTime.Now.Ticks / 10000000;
        // 遍历,把时间已到达超过超时时间的找出来
        lock (_DictionaryLocker)
        {
            foreach (var key in _TaskIdDictionary.Keys.ToList())
            {
                var task = _TaskIdDictionary[key];
                if (_TaskIdDictionary[key].ExecuteSecondTicks <= secondTicks)
                {
                    // 加入超时任务执行队列,并移除清单
                    lock (_RunLocker)
                    {
                        _TaskRunQueue.Enqueue(task);
                        RemoveTimeoutTask(task.TaskId);
                    }
                    // 有生产,则通知执行线程(消费者)
                    _WaitHandle.Set();
                }
            }
        }
    }

3.6任务执行者

执行队列中存在任务时就执行,否则等待。线程等待,这里使用了 EventWaitHandle,EventWaitHandle.WaitOne 等待,生产者使用 EventWaitHandle.Set 方法进行通知,配合起来有效地运行队列中的任务。

    // 超时任务执行线程
    private Thread _TaskRunThread;
    // 用于同步操作任务队列的线程信号(生产者,消费者通知作用)
    private EventWaitHandle _WaitHandle = new AutoResetEvent(false);
    // 用于退出执行线程的一个标识
    private bool _Working = true;

    /// <summary>
    /// 超时任务执行线程主体
    /// </summary>
    private void TaskRunning()
    {
        while (_Working)
        {
            TimeoutTask<T> task = null;
            lock (_RunLocker)   
            {
                if (_TaskRunQueue.Count > 0)
                {
                    task = _TaskRunQueue.Dequeue();  
                }
            }
            // 存在超时任务执行其回调
            if (task != null)
            {
                task.Callback(task.ObjectKey);
            }
            else
            {
                // 等待生产者通知
                _WaitHandle.WaitOne();
            }
        }
    }

3.7向外开放的接口

代码如是说:

   /// <summary>
    /// 指定对象标识,超时时长(秒为单位),超时执行回调,加入到超时检测字典中
    /// </summary>
    /// <param name="objectKey"></param>
    /// <param name="timeoutSeconds"></param>
    /// <param name="callback"></param>
    /// <returns></returns>
    public long AddTimeoutTask(T objectKey, int timeoutSeconds, TimeoutCallback<T> callback)
    {
        TimeoutTask<T> task = new TimeoutTask<T>();
        task.ObjectKey = objectKey;
        task.TimeoutSeconds = timeoutSeconds;
        task.Callback = callback;
        long taskId = GetNextTaskId();
        task.TaskId = taskId;
        task.ExecuteSecondTicks = DateTime.Now.Ticks / 10000000 + timeoutSeconds;

        lock (_DictionaryLocker)
        {
            // 以任务标识为主键的任务清单
            _TaskIdDictionary[taskId] = task;
            // 以对象标识为主键的任务清单
            if (_TaskObjectKeyDictionary.ContainsKey(objectKey))
            {
                _TaskObjectKeyDictionary[objectKey].Add(task);
            }
            else
            {
                List<TimeoutTask<T>> list = new List<TimeoutTask<T>>();
                list.Add(task);
                _TaskObjectKeyDictionary[objectKey] = list;
            }
        }
        return taskId;
    }

    /// <summary>
    /// 根据对象标识移除超时任务设置
    /// </summary>
    /// <param name="objectKey"></param>
    public void RemoveTimeoutTask(T objectKey)
    {
        lock (_DictionaryLocker)
        {
            if (_TaskObjectKeyDictionary.ContainsKey(objectKey))
            {
                // 在任务标识为主键的清单中移除相应的该对象的多个超时任务
                foreach (var task in _TaskObjectKeyDictionary[objectKey])
                {
                    _TaskIdDictionary.Remove(task.TaskId);
                }
                _TaskObjectKeyDictionary[objectKey].Clear();
            }
        }
    }

    /// <summary>
    /// 根据任务标识移除超时任务设置
    /// </summary>
    /// <param name="taskId"></param>
    public void RemoveTimeoutTask(long taskId)
    {
        lock (_DictionaryLocker)
        {
            if (_TaskIdDictionary.ContainsKey(taskId))
            {
                var task = _TaskIdDictionary[taskId];
                _TaskIdDictionary.Remove(taskId);
                // 在对象标识为主键的清单移除相应的超时任务
                _TaskObjectKeyDictionary[task.ObjectKey].Remove(task);
            }
        }
    }

4.应用示例

定义回调处理方法,添加一个超时任务只需要指定简单的参数即可,如下示例,会按什么顺序输出什么呢?

class Program
{
    static void Main(string[] args)
    { 
        TS.Task.TimeoutTaskRunner<string> runner = new TS.Task.TimeoutTaskRunner<string>();

        TS.Task.TimeoutCallback<string> callback = (string key) =>
        {
            Console.WriteLine(key + " is timeout.");
        }; 

        runner.AddTimeoutTask("a", 4, callback);
        runner.AddTimeoutTask("b", 3, callback);
        runner.AddTimeoutTask("c", 2, callback); 

        Console.ReadKey();
        runner.Dispose();
    }
}

运行结果:

5.小结

超时处理在异步通信中经常会碰到,实现超时处理的通用机制,能有效的复用代码,提高效率。代码仍然有很多优化空间,如遍历检测超时是否有更合适的的方式等,欢迎探讨!

完整代码请访问:

https://github.com/triplestudio/TimeoutTask

Guess you like

Origin www.cnblogs.com/timeddd/p/10938083.html