2

We have a critical block of code. In the case that our software has multiple threads we can use lock statement to enter that block of code. When we need reentrancy we can use Mutex objects. What about when we use tasks and async functions in our software? Which alternative to lock and Mutex can use to enter a block of code specially when our method might be called recursively?

I need:

  • only one task can be entered a block of code
  • the same task is allowed to enter the block again before exiting the block
  • being task based not thread based
1
  • I would really love to see the code that you're talking about and see if there is a way to refactor to avoid the locking. Commented Aug 7, 2023 at 1:32

2 Answers 2

2

You can use SemaphoreSlim and its WaitAsync method to implement mutual exclusion between tasks.

This doesn't allow reentrancy, and fundamentally that's because there's no standard way to get "the current task", and so no way for any kind of mutex implementation to independently remember which task owns the mutex.

If you need reentrancy, you'll have to engineer this yourself. In most cases the easiest way to do this will be to keep a variable in your task that remembers how many times it has entered the critical section.

Sign up to request clarification or add additional context in comments.

1 Comment

It's a pity that they have not predict such a scenario. .Net always have been great. But I made my own class, and it seems it works well.
0

Finally I implemented this class to achieve what I need.

public class AsyncMutex
{
    public int key;
    public AsyncLocal<int> keyLocal = new AsyncLocal<int>();
} 

public class WaitReleaseSemaphore : IDisposable
    {
        private readonly AsyncMutex asyncMutex;
        private int timeout;
        private bool caughtByThis = false;
        public WaitReleaseSemaphore(AsyncMutex sema, int timeout = 600000)
        {
            asyncMutex = sema;
            this.timeout = timeout;
        }

        public async Task<WaitReleaseSemaphore> Wait(Func<bool,Task> action)
        {
            using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource())
            {
                cancellationTokenSource.CancelAfter(timeout);

            START:
                if (cancellationTokenSource.IsCancellationRequested)
                {
                    await action(true);
                    //Dispose();
                    return this;
                }

                int prevKey = Interlocked.CompareExchange(ref asyncMutex.key, 1, 0);
                if (prevKey == 0) //AsyncMutex was free, and we can take it and execute the function
                {
                    asyncMutex.keyLocal.Value++;
                    caughtByThis = true;
                    await action(false);
                    //Dispose();
                }
                else //we should wait for other tasks                    
                {
                    prevKey = Interlocked.CompareExchange(ref asyncMutex.key, asyncMutex.key + 1, asyncMutex.keyLocal.Value);
                    if (prevKey == asyncMutex.keyLocal.Value) //the AsyncMutex is caught by current task, so we can continue
                    {
                        asyncMutex.keyLocal.Value++;
                        caughtByThis = true;
                        await action(false);
                        //Dispose();
                    }
                    else if (asyncMutex.key < asyncMutex.keyLocal.Value)
                        throw new Exception("key is smaller than keyLocal. So, something is wrong");
                    else
                    {
                        await Task.Delay(50);
                        goto START;
                    }
                }

                return this;
            }
        }

        public void Dispose()
        {
            if (caughtByThis)
                Interlocked.Decrement(ref asyncMutex.key);
        }
    }

How to use:

var t1 = Task.Run(async () =>
            {
                using (await new WaitReleaseSemaphore(mutex).Wait(async (timedOut) =>
                {
                    await Task.Delay(3000);
                    Debug.WriteLine("TT" + sw.ElapsedMilliseconds);
                })) ;
            });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.