-2

Context: primitive chat bot.

I have a simple code:

private static bool busy;
private async void OnTimedEvent(object sender, ElapsedEventArgs e)
{
    if (!busy)
    {
        busy = true;
        //some logic
        await SomeAsyncCommand();
        busy = false;
    }
    else
    {
        await Reply("Stuff's busy yo"); // falling thru, no need to process every request
    }
}

And it works fine so far, I haven't encountered any "non-atomic precision issues" with bool yet. The issue arises when I start to do more complex stuff in async/await context, for example:

public async Task AddEntry(string url, DateTime time, User user)
{
    UpdateUser(user);
    // We cant fall thru here, all sent requests MUST be processed so we wait
    while (busy)
    {
        await Task.Delay(100); // checking every 100ms if we can enter
    }
    busy = true;
    // working with NON-CONCURRENT collection
    // can await as well
    busy = false;
}

My understanding is - as these threads pile up waiting for the "boolean lock" to be released, there could be a case when two of them will enter at the same time which is kill.

I know I cant use lock in the await/async context (and I also read somewhere here that CLR lock is a bad practice in async/await env in general?) and I know that bool isnt good alternative as well.

How are these situations usually handled?

Digika
  • 127
  • 2
  • 11

2 Answers2

0

You better look at SemaphoreSlim and Interlocked.CompareExchange
For example

private static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(0, 1);
private async void OnTimedEvent(object sender, ElapsedEventArgs e)
{
    bool busy = await _semaphoreSlim.WaitAsync(0); //instantly return with false if busy
    if(!busy)
    {
        try
        {
            //some logic
            await SomeAsyncCommand();
        }
        finally
        {
            _semaphoreSlim.Release();
        }
    }
    else
    {
        await Reply("Stuff's busy yo"); // falling thru, no need to process every request
    }
}

and

private static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(0, 1);
public static async Task AddEntry(string url, DateTime time, User user)
{
    UpdateUser(user);
    await _semaphoreSlim.WaitAsync();
    try
    {
        // do job
    }
    finally
    {
        _semaphoreSlim.Release();
    }
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
khoroshevj
  • 1,077
  • 9
  • 15
  • To the downvoter, I am not sure what you thought was wrong but this is the correct answer to the problem. To "lock" while using async you need to use a `SemaphoreSlim` with 1 slot available. – Scott Chamberlain Feb 27 '18 at 18:18
  • I added a 2nd example showing the OP's first example using a sempaphore, i hope that was ok. – Scott Chamberlain Feb 27 '18 at 18:23
  • Yeah, I was looking at Semaphore but wasnt sure if the context was relevant. Follow-up question: is it possible to specify wait time like with `System.Threading.Monitor.TryEnter(mylock, 2000)` i.e. if I want it to fall through not instantly but after some time? – Digika Feb 27 '18 at 18:25
  • @Digika look at the first example, the `0` is the wait time. `bool busy = await _semaphoreSlim.WaitAsync(2000);` would cause it to wait for 2 sec. before returning with a false if it was busy. – Scott Chamberlain Feb 27 '18 at 18:26
-1

There aren't a bunch of threads piling up. The method will run sequentially as long as you await async calls, it will just yield to something else that can run elsewhere whilst the Task.Delay runs in this scenario. So there is no need to use a lock at all.

gmn
  • 4,199
  • 4
  • 24
  • 46
  • So in other words you are saying it is a valid approach here? A bit confused about your `There aren't a bunch of threads piling up.` The `AddEntry` can be called up to 100 times in less than 500ms, as I understand each call spawns thread in threadpool, no? – Digika Feb 27 '18 at 18:15
  • 1
    @Digika it depends on the syncronisation context. Using a `SemaphoreSlim` like khoroshevj suggests is guaranteed to work correctly nomatter the sync context you are under. – Scott Chamberlain Feb 27 '18 at 18:16
  • @Digika ^ what he said. Given a standard synchronisation context, my understanding is that it would probably just keep executing, waiting, then executing within the while loop, rather than continuously spawning threads. Preserving the order of execution for the method. – gmn Feb 27 '18 at 18:21