Quantcast
Channel: Randy Riness @ SPSCC aggregator
Viewing all articles
Browse latest Browse all 3015

MSDN Blogs: Delay task

$
0
0

Consider there is a scenario like that a user can search something by typing words in a textbox. The app will send request to the server for query result. But you may want to delay the request sending because the user is still typing. You will send the latest information after the user stop to type after a short while and ignore the ones during typing.

To abstract this, we can design a delay task with following goals to implement it.

  • Generates a task to control when to run a given handler.
  • Process the handler after a specific time span when the task starts.
  • The task can be run by several times.
  • Do not process the handler if the task is run again before it processes.

I will introduce this implementation in Type Script and C#.

Type Script

Let's define the options interface for the delay task.

/**  * Options to set the delay task.  */export interface DelayOptionsContract {
 
    /**      * The business handler to process.      * @param modelArg  An optional argument.      */    process(modelArg?: any): void;
 
    /**      * The timespan in millisecond for waiting.      */    delay?: number;
 
    /**      * The object to be used as the current object      * for handler processing.      */    thisArg?: any;
}

Then we need add a function that generates the task by the way described by the options.

export function delay(options: DelayOptionsContract)    : (modelArg?: any) => void {    // ToDo: Implement it.    return null;}

Sounds we need implement it by this way.

return (modelArg?: any) => {    setTimeout(() => {        options.process(modelArg);    }, options.delay);};

However, it will process twice or more when the user run the task several times between the time span less than the one in options. So we need update it to add the status and checking to ignore this. We can just use an increasable number as the status value for checking. We need also add a new handler for processing the handler registered after status checking.

var token = 0;var handler = (modelArg: any, testToken: number) => {    if (testToken !== token)return;    options.process.call(options.thisArg, modelArg);};

The function should return following result to call the new handler.

return (modelArg?: any) => {    token++;    setTimeout(handler, options.delay,modelArg,token);};

For a better solution, we can clear the timeout callback when the task is run again.

var timeout: number;return (modelArg?: any) => {    token++;    clearTimeout(timeout);    if (!!timeout)        clearTimeout(timeout);    timeout = setTimeout(        handler,        options.delay,        modelArg,        token);};

Clearing the timeout token should be also added in the new handler. After add the empty arguments checking, it will look like this.

/**  * Generates a delay task.  * @param options  The options to load.  */export function delay(options: DelayOptionsContract)    : (modelArg?: any) => void {    if (!options || !options.process)        return null;    var token = 0;    var timeout: number;    var handler = (modelArg: any, testToken: number) => {        timeout = null;        if (testToken !== token)            return;        options.process.call(options.thisArg, modelArg);    };    return (modelArg?: any) => {        token++;        clearTimeout(timeout);        if (!!timeout)            clearTimeout(timeout);        timeout = setTimeout(            handler,            options.delay != null ? options.delay : 0,            modelArg, token);    };}

Now we can have a test.

var task = delay({    delay: 300,    process: function (model) {        console.debug(model);    }});document.body.addEventListener(    "click",    function (ev) {        task(new Date().toLocaleTimeString());    },    false);

You will see it works well. And of course, you can continue to add promise support.

C#

Firstly, we need add some references for using.

using System;
using System.Threading;
using System.Threading.Tasks;

Then we can define a class with a property to set the timeout span and an event for registering the one which will be occurred after the task processing. An async method is provided to call to process the task after the specific time span.

public class DelayTask
{
    public TimeSpan Span { getset; }
 
    public event EventHandler Processed;
 
    public async Task<bool> Process()
    {
        throw new NotImplementedException();
    }
 
}

In process method, we need delay to execute and raise the event. To ignore the processing of the ones raised before the previous one finished, we need add a token to check.

private Guid _token = Guid.Empty;

The token should be generated, checked and cleared during processing. The result is a status indicating whether it executes.

public async Task<bool> Process()
{
    if (Span == null) Span = new TimeSpan(0);
    var token = Guid.NewGuid();
    _token = token;
    await Task.Delay(Span);
    if (token != _token) return false;
    _token = Guid.Empty;
    Processed(thisnew EventArgs());
    return true;
}

And add a cancellable process method. Now we get the following task.

/// <summary>/// The delay task./// </summary>public class DelayTask
{
    private Guid _token = Guid.Empty;
 
    /// <summary>    /// Gets or sets the delay time span.    /// </summary>    public TimeSpan Span { getset; }
 
    /// <summary>    /// Adds or removes the event handler occurred    /// after processed.    /// </summary>    public event EventHandler Processed;
 
    /// <summary>    /// Processes the delay task.    /// </summary>    /// <returns>    /// A task with a value indicating whether it executes.    /// </returns>    public async Task<bool> Process()
    {
        return await Process(CancellationToken.None);
    }
 
    /// <summary>    /// Processes the delay task.    /// </summary>    /// <param name="cancellationToken">    /// The cancellation token that will be checked prior    /// to completing the returned task.    /// </param>    /// <returns>    /// A task with a value indicating whether it executes.    /// </returns>    public async Task<bool> Process(
        CancellationToken cancellationToken)
    {
        if (Span == null) Span = new TimeSpan(0);
        var token = Guid.NewGuid();
        _token = token;
        await Task.Delay(Span, cancellationToken);
        if (token != _token) return false;
        _token = Guid.Empty;
        if (cancellationToken.IsCancellationRequested)
            return false;
        Processed(thisnew EventArgs());
        return true;
    }
}

To use it, you just need create an instance of this class with delay time span and call its process member method where you need execute the task. You can also register the event handler for processing.


Viewing all articles
Browse latest Browse all 3015

Trending Articles