Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ensure a long running task is only fired once and subsequent request are queued but with only one entry in the queue

I have a compute intensive method Calculate that may run for a few seconds, requests come from multiple threads.

Only one Calculate should be executing, a subsequent request should be queued until the initial request completes. If there is already a request queued then the the subsequent request can be discarded (as the queued request will be sufficient)

There seems to be lots of potential solutions but I just need the simplest.

UPDATE: Here's my rudimentaryattempt:

private int _queueStatus;
private readonly object _queueStatusSync = new Object();

public void Calculate()
{
    lock(_queueStatusSync)
    {
        if(_queueStatus == 2) return;
        _queueStatus++;
        if(_queueStatus == 2) return;
    }
    for(;;)
    {
        CalculateImpl();
        lock(_queueStatusSync)
            if(--_queueStatus == 0) return;

    }
}

private void CalculateImpl()
{
    // long running process will take a few seconds...
}
like image 253
Dog Ears Avatar asked Sep 30 '22 03:09

Dog Ears


1 Answers

The simplest, cleanest solution IMO is using TPL Dataflow (as always) with a BufferBlock acting as the queue. BufferBlock is thread-safe, supports async-await, and more important, has TryReceiveAll to get all the items at once. It also has OutputAvailableAsync so you can wait asynchronously for items to be posted to the buffer. When multiple requests are posted you simply take the last and forget about the rest:

var buffer = new BufferBlock<Request>();
var task = Task.Run(async () =>
{
    while (await buffer.OutputAvailableAsync())
    {
        IList<Request> requests;
        buffer.TryReceiveAll(out requests);
        Calculate(requests.Last());
    }
});

Usage:

buffer.Post(new Request());
buffer.Post(new Request());

Edit: If you don't have any input or output for the Calculate method you can simply use a boolean to act as a switch. If it's true you can turn it off and calculate, if it became true again while Calculate was running then calculate again:

public bool _shouldCalculate;

public void Producer()
{
    _shouldCalculate = true;
}

public async Task Consumer()
{
    while (true)
    {
        if (!_shouldCalculate)
        {
            await Task.Delay(1000);
        }
        else
        {
            _shouldCalculate = false;
            Calculate();

        }
    }
}
like image 105
i3arnon Avatar answered Oct 21 '22 03:10

i3arnon