-11

Assume we have a method like this

public static void method(string param)
{
    ** critical section **
    // There are a lot of methods calls 
    // switch cases 
    // if conditions 
    // read and write in dictionary 
    // new class initiations
    ** critical section **  
}

how we can make it thread-safe while thousand of concurrent calls happen?

Could delegates help? I read here that

Modifying event is not thread-safe, but invoking a Delegate is thread-safe. Since a Delegate is immutable type so it is thread safe.

Does that mean that delegates make my code thread-safe?

If delegate doesn’t grantee thread-safe concurrent calls. Can you explain why?

If Lock grantee thread-safe as it is for then:

  • How to avoid Deadlock and release lock after a specific timeout?
  • Mutex is similar to Lock in some aspects.is Lock or Mutex faster?

For better performance tuning, Dose Visual Studio have the ability to analyze where shared resources are?

A Farmanbar
  • 4,381
  • 5
  • 24
  • 42
  • 7
    It depends on what the lines of code do. As it is right now, your method is thread-safe. – RobertBaron Jul 22 '19 at 11:11
  • 2
    `read and write in dictionary` Dictionary is not thread-safe. – mjwills Jul 22 '19 at 11:19
  • 7
    There are many reasons why a method can be non thread safe. You should be more concrete, and ask how to solve concrete problems which make your method non thread safe. It's impossible to explain them all. – JotaBe Jul 22 '19 at 11:25
  • `Does that mean that delegates make my code thread safe?` No. Delegates don't make things thread-safe. – mjwills Jul 22 '19 at 12:11
  • 1
    _Methods_ do not need to be thread safe. Methods are read-only. Nothing can harm a method. Thread safety is always about protecting _data_ that are shared between threads. Sometimes the shared data are only ever accessed by a single method, and then we can get away with saying "method" when we really should say "data." But, if more than one method accesses the same shared data, then it's important to remember that the data must be protected in _every_ method that accesses it---even in methods that only _read_ the data. – Solomon Slow Jul 22 '19 at 13:54
  • @SolomonSlow what about data that was initiated locally ? is it shared data?does it need safety ? – A Farmanbar Jul 22 '19 at 15:42
  • 1
    I don't know what "initiated locally" means. All threads, by definition, see the same virtual address space, but when I say "shared" I am talking about variables that are actually used by more than one thread. Are you asking about local variables (i.e., variables declared inside the body of a function?) In many programming languages, there is no way for one thread to access the local variables of another thread. However, in a language like C#, many of your local variables will just be references to heap objects that potentially could be shared. It's up to the developer to know which is which. – Solomon Slow Jul 22 '19 at 17:19
  • @SolomonSlow yea , you are correct. nice suggestions and explain.thanks – A Farmanbar Jul 22 '19 at 17:22
  • 2
    `but i want the thread which acquired the lock do release lock in 2500 mili-sec time` This is _likely_ an odd thing to do. If you need a lock, you need a lock. Releasing it too early may cause unexpected behaviour. Again, I strongly recommend creating a [mcve]. – mjwills Jul 22 '19 at 21:37
  • Do you know how long does it takes to execute your method(string param) ? – Surjit Samra Jul 23 '19 at 07:45
  • @SurjitSamra approximately 3000-2500 mil-sec some times more than 5 minutes – A Farmanbar Jul 23 '19 at 08:25
  • 1
    A method is thread-safe when the access to all resources, that are shared between threads, is synchronized. Different resources may require different techniques. Since you are talking about a `static` method, some shared resources might be static too. In this case it will be more complicated to implement thread-safety. Simple locking will only work in the scope of the acquired lock. `public static` members are globally accessible while the lock applies locally. If you want serious solutions please provide details about the shared resources. Thread-safety can be challenging. – BionicCode Jul 23 '19 at 19:42
  • 3
    Maybe you find some solutions or inspiration here: [Threading in C# Joseph Albahari](http://www.albahari.com/threading/part2.aspx#_Thread_Safety). It is very informative and a recommended read. – BionicCode Jul 23 '19 at 19:44
  • 1
    _"There are a lot of methods calls"_ All the internals of each of those methods are important details. – BionicCode Jul 23 '19 at 19:51
  • @BionicCode thanks for document and recommendation. – A Farmanbar Jul 24 '19 at 09:03
  • Looks like you changed your question a lot since the original question. Some answers now look out of context. I will delete my answer to keep SO clean . – Surjit Samra Jul 25 '19 at 11:12
  • @SurjitSamra i just put extra concerns in addition to original question and i did put good bounty. – A Farmanbar Jul 25 '19 at 14:35
  • Your example code had "** critical section **" added in an earlier edit. Does this mean you have a lock / mutex around the whole method body? This makes it unclear why your method is non-thread-safe, but I can see how this change would reduce throughput. – Peter Wishart Jul 25 '19 at 18:02
  • @Peter Wishart ,in the diagram I put the details.the method is not safe because different parts of the server have access and call it .here is a race condition.and lock makes sometimes deadlock because this method might takes long time to process because of cpu busy. – A Farmanbar Jul 25 '19 at 18:48
  • The current diagram looks like its showing a choice between locking around either _the only call to `Method()`_ or _the body of `Method()`_, it sounds like I'm misunderstanding it. – Peter Wishart Jul 26 '19 at 14:11
  • @PeterWishart yea it's a choice between locking around either the only call to Method() or the body of Method() – A Farmanbar Jul 26 '19 at 14:33
  • You said that different parts of the server can also call the method though? Waiting a long time due to locking is `contention` rather than `deadlock`. A deadlock usually involves more than one lock, although that lock may not be obvious e.g. exclusive access to a file or socket. – Peter Wishart Jul 26 '19 at 15:16

5 Answers5

4

Is Lock or Mutex faster?

using System;
using System.Diagnostics;
using System.Threading;

namespace LockingTest
{
    class Program
    {
        public static object locker = new object();
        public static Mutex mutex = new Mutex();
        public static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            sw.Restart();
            for (int i = 0; i < 10000000; i++)
            {
                mutex.WaitOne(); // we are testing mutex time overhead
                mutex.ReleaseMutex();
            }
            sw.Stop();
            Console.WriteLine("Mutex :" + "  proccess time token " + sw.Elapsed.ToString() + " miliseconds");
            Thread.Sleep(1000); // let os to be idle 
            sw.Restart();
            for (int i = 0; i < 10000000; i++)
            {
                lock (locker) { } // we are testing lock time overhead
            }
            sw.Stop();
            Console.WriteLine("Lock :" + "  proccess time token " + sw.Elapsed.ToString() + " miliseconds");           
            Console.ReadLine();
        }
    }
}

if you copy and paste above code in visual stuido and run it you will see Lock_vs_mutex

as you can see lock is 50x faster than mutex

How is the shared resource part of the code determined?

For better performance tuning, Dose Visual Studio have the ability to analyze where shared resources are?

i have updated my visual studio 2010 to 2015,in visual studio 2015, when you look top of each method you will see references look below image. enter image description here>

When references to a method goes high the danger of memory corruption goes high and vise versa.

How to avoid Deadlock and release lock after a specific timeout

using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Threading.Tasks;
    namespace LockReleaseTest
    {
        class Program
        {
            public static object locker = new object();
            public static ManualResetEvent mre = new ManualResetEvent(false);
            public static bool isWorkDone = false;
            public class StateObject
            {
                public int ThreadNumber;
                public string Criticla_Parameter;
                public int ItTakes = 1000;
            }
            static void Main(string[] args)
            {
                for (int i = 0; i < 5; i++)
                {
                    StateObject state = new StateObject();
                    state.ThreadNumber = i;
                    state.Criticla_Parameter = "critical " + i.ToString();
                    ThreadPool.QueueUserWorkItem(method, state);
                }
                Thread.Sleep(13000); // wait previous process to be done
                Console.WriteLine("In order to test release lock after 2.5 sec press enter");
                Console.ReadLine();
                for (int i = 0; i < 5; i++)
                {
                    StateObject state = new StateObject();
                    state.ThreadNumber = i;
                    state.ItTakes = (i + 1) * (1000);
                    state.Criticla_Parameter = "critical " + i.ToString();
                    ThreadPool.QueueUserWorkItem(method2, state);
                }
                Console.ReadLine();
            }
            public static void method(Object state)
            {
                lock (locker)
                {
                    // critcal section
                    string result = ((StateObject)state).Criticla_Parameter;
                    int ThreadNumber = ((StateObject)state).ThreadNumber;
                    Console.WriteLine("Thread " + ThreadNumber.ToString() + " entered");
                    // simultation of process            
                    Thread.Sleep(2000);
                    Console.WriteLine("ThreadNumber is " + ThreadNumber + " Result of proccess : " + result);
                    // critcal section
                }
            }
            public static void method2(Object state)
            {
                if (Monitor.TryEnter(locker, -1))
                {
                    mre.Reset();
                    ThreadPool.QueueUserWorkItem(criticalWork, state);
                    Thread.Sleep(200);
                    ThreadPool.QueueUserWorkItem(LockReleaser, ((StateObject)state).ThreadNumber);
                    mre.WaitOne();
                    Monitor.Exit(locker);
                }
            }
            public static void criticalWork(Object state)
            {
                isWorkDone = false;
                string result = ((StateObject)state).Criticla_Parameter;
                int ThreadNumber = ((StateObject)state).ThreadNumber;
                int HowMuchItTake = ((StateObject)state).ItTakes;
                // critcal section
                Console.WriteLine("Thread " + ThreadNumber.ToString() + " entered");
                // simultation of process            
                Thread.Sleep(HowMuchItTake);
                Console.WriteLine("ThreadNumber " + ThreadNumber + " work done. critical parameter is : " + result);
                isWorkDone = true;
                mre.Set();
                // critcal section
            }
            public static void LockReleaser(Object ThreadNumber)
            {
                Stopwatch sw = new Stopwatch();
                sw.Restart();
                do
                {
                    if (isWorkDone) return; // when work is done don't release lock // continue normal
                } while (sw.Elapsed.Seconds <= 2.5); // timer in order to release lock
                if (!isWorkDone) // more than 2.5 sec time took but work was not done
                {
                    Console.WriteLine("ThreadNumber " + ThreadNumber + " work NOT done. Lock must be released ");
                    mre.Set();
                }
            }
        }
    }

If you copy and paste the above code in visual studio and run it you will get a result looks like this

Release lock program result

As you can see ,in the first processes we do not release lock and all the threads enters sequentially to critical section but in the second process we do release the lock when process goes long time and when the lock is released next thread(Thread 2) enters and acquires the lock. Because, the lock must be released in parent thread then we use ManualEventRest to signal the parent to release the lock. i tried other approaches but they didn't work and exception of SynchronizationLockException happens .This is the best approach that i found without throwing exception.

If this post is useful don't forget to vote up.sincerely yours

Community
  • 1
  • 1
A Farmanbar
  • 4,381
  • 5
  • 24
  • 42
  • hmm ,Look like you have solved your problem but also making some strong assumptions , any way try this code in production and let us know how did it go. – Surjit Samra Jul 26 '19 at 21:18
  • @SurjitSamra yea i did i had to because i got down vote instead of answer. :)) – A Farmanbar Jul 26 '19 at 21:20
  • ahh well if you are happy with your code then problem is solved , but would be interested to see if some one with great experience with threads etc. comes back with some solid answer. I will upvote you for your effort :) – Surjit Samra Jul 26 '19 at 21:26
  • In method2, the criticalWork could easily execute outside of the Monitor.TryEnter/Exit (and outside of the mre.Reset/WaitOne) because it is only being queued for execution at a later time, there is no guarantee as to when it will actually execute. So I don't see this lock approach working. Also, in method2, if you forcefully release the lock because one of the tasks is taking a long time, what will happen if that task later tries to access the critical resource? Overall, this is also a complicated way to do things. Please see my second answer below. – sjb-sjb Jul 26 '19 at 22:51
  • To see how this fails, add "static int _simultaneous = 0;" to the class and then in criticalWork add "Interlocked.Add(ref Program._simultaneousUsers, 1); Console.WriteLine($"Simultaneous: {_simultaneousUsers}");" before the Sleep, and add "Interlocked.Add(ref Program._simultaneousUsers, -1);" after the Sleep. When you run it you will see more than one simultaneous user in the critical section. – sjb-sjb Jul 27 '19 at 02:59
  • Would ask that you accept my second answer below as correct. – sjb-sjb Jul 27 '19 at 03:01
  • @sjb-sjb it's all about releasing the lock not releasing +safety .as others said .it will have consequences .i asked how to release not how to release and made it safe. – A Farmanbar Jul 27 '19 at 07:18
2

I am taking the liberty of adding a second answer as it now appears that a key part of the question was how to cancel a lock (i.e. release it after a few seconds).

However, it doesn't make sense to cancel a lock (from "outside" of the lock) without cancelling the work that is being done inside the lock. If you do not cancel the work that is being done inside the lock then it may attempt continued access to the critical resource, resulting in two threads using the resource at the same time. What one should do, instead of breaking the lock from outside, one should cancel the work being done, which then will result in the lock being exited by that worker.

A comment on threading and cancellation. One should not abort threads, because in general it leaves the program (e.g. the resources held by that thread) in an undefined state. It has been a number of years since tasks and task cancellation have been introduced. A Task is essentially an operation or method that is queued to be executed, along with other Tasks, on threads obtained from, for example, the thread pool. These days pretty much all recent code should be based on tasks and follow the cooperative task cancellation approach. The following code shows how to do this, including starting the task on the thread pool.

Note I am using the MethodLock class introduced in my earlier answer; this is just a wrapper for SemaphoreSlim.

Here is a Worker class that does some work with a critical resource (and some without the resource). It cooperates in cancellation by testing the CancellationToken every once in a while. If cancellation was requested then the worker cancels itself by throwing a special exception.

        public class Worker
        {
            public Worker(int workerId, CancellationToken ct, int howMuchWorkToDo)
            {
                this.WorkerId = workerId;
                this.CancellationToken = ct;
                this.ToDo = howMuchWorkToDo;
                this.Done = 0;
            }
            public int WorkerId { get; }
            public CancellationToken CancellationToken { get; }
            public int ToDo { get; }
            public int Done { get; set; }

            static MethodLock MethodLock { get; } = new MethodLock();

            public async Task DoWorkAwareAsync()
            {
                this.CancellationToken.ThrowIfCancellationRequested();
                this.Done = 0;
                while (this.Done < this.ToDo) {
                    await this.UseCriticalResourceAsync();
                    await this.OtherWorkAsync();
                    this.CancellationToken.ThrowIfCancellationRequested();
                    this.Done += 1;
                }
                Console.WriteLine($"Worker {this.WorkerId} completed {this.Done} out of {this.ToDo}");
            }

            private async Task UseCriticalResourceAsync()
            {
                using (await MethodLock.LockAsync()) {
                    //Console.WriteLine($"Worker {this.WorkerId} acquired lock on critical resource.");
                    await Task.Delay(TimeSpan.FromMilliseconds(50));
                }
            }
            private async Task OtherWorkAsync()
            {
                await Task.Delay(TimeSpan.FromMilliseconds(50));
            }
        }

Now let's look at how to start a number of background workers and keep them from running too long, i.e. cancelling them after a few seconds. Note this is set up as a console application.

The tasks are put onto the thread pool, which means that the system will allocate the tasks among available threads. The system can also dynamically reallocate the tasks to threads if needed, for example if a task is queued to a thread that is busy while another thread becomes free.

        static void Main(string[] args)
        {
            Random rand = new Random( DateTime.Now.Millisecond);

            Console.WriteLine("---- Cancellation-aware work");
            Task[] tasks = new Task[10];
            for (int i = 0; i < 10; i++) {
                CancellationTokenSource cts = new CancellationTokenSource();
                cts.CancelAfter(TimeSpan.FromMilliseconds(2000));
                int howMuchWork = (rand.Next() % 10) + 1;
                Worker w = new Worker(i, cts.Token, howMuchWork);
                tasks[i] = Task.Run(
                    async () => {
                        try {
                            await w.DoWorkAwareAsync();
                        } catch (OperationCanceledException) {
                            Console.WriteLine($"Canceled worker {w.WorkerId}, work done was {w.Done} out of {w.ToDo}");
                        }
                    },
                    cts.Token
                );
            }
            try {
                Task.WaitAll(tasks);
            } catch (AggregateException ae) {
                foreach (Exception e in ae.InnerExceptions) {
                    Console.WriteLine($"Exception occurred during work: {e.Message}");
                }
            }
            Console.ReadKey();
        }

I would comment that the presence of "cts.Token" as the second argument to Task.Run does NOT relate to a forced / hard cancellation of the task created by the Task.Run method. All that Task.Run does with this second argument is compare it to the cancellation token inside the cancellation exception, and if it is the same then Task.Run transitions the task to the Canceled state.

When you run this you'll see something like the following:

    ---- Cancellation-aware work
    Worker 5 completed 1 out of 1
    Worker 2 completed 1 out of 1
    Worker 8 completed 1 out of 1
    Worker 6 completed 3 out of 3
    Worker 7 completed 3 out of 3
    Canceled worker 3, work done was 4 out of 5
    Canceled worker 4, work done was 4 out of 10
    Canceled worker 1, work done was 4 out of 8
    Canceled worker 9, work done was 4 out of 7
    Canceled worker 0, work done was 5 out of 9

Again, this design assumes that the worker methods cooperate with the cancellation. If you are working with legacy code where the worker operation does not cooperate in listening for cancellation requests then it may be necessary to create a thread for that worker operation. This requires proper cleanup and in addition it can create performance problems because it uses up threads, which are a limited resource. The response by Simon Mourier in the middle of this linked discussion shows how to do it: Is it possible to abort a Task like aborting a Thread (Thread.Abort method)?

sjb-sjb
  • 1,112
  • 6
  • 14
  • You codes and descriptions are very good .but you must consider that post answer around the question and its topic field .in the question i didn't talk about tasks.and i test your previous answer it works but not perfect although i had 3 hours trouble using tasks.you answer desire 1+ vote.because it's helpful. – A Farmanbar Jul 27 '19 at 07:15
  • 1
    Hi, well I think the feedback you are getting in this forum, both my responses and others, is about how to do it right. Look at what RobertBrown is saying for example, Jul 22 he is saying the same thing as I am saying here -- set a variable and the worker thread or task checks it to abort. On your comment "I asked how to release [the lock] not how to release and made it safe", clearly the advice you are getting is to do things safely and in the right way. Most people don't see the point of having code that is not 'safe' i.e. doesn't work. – sjb-sjb Jul 27 '19 at 15:04
  • combine your 2 answers to 1 and brief your explains max 2 lines for each section and try to add link instead of making fat answers then i gonna reward repu to you.i am waiting – A Farmanbar Oct 21 '19 at 10:46
  • I believe I've already given you some pretty good material here. – sjb-sjb Oct 23 '19 at 02:34
1

A lot of questions have been asked, but I will try to address all of them.

How we can make it thread safe while thousand of concurrent calls happen?

To make a method entirely thread safe, you can write it so that it has no side-effects. A method with no side-effects would not access any shared resources.


Could delegates help? Does that mean that delegates make my code thread safe? When do delegates come into play for thread safety ?

Delegates in C# are similar to function pointers in c++. They allow you to assign a method to a variable, and then call that method by invoking it through that variable. The only "thread safe" guarantee you get from using delegates is that the moment the delegate is invoked it will successfully call the function assigned to it. The invoked function executes exactly as it would if you had hard-coded a call to it instead in the same place.


in the above diagram, what's the correct use of Locker? Inside of method or outside of it? why?

I would argue that both options are less than ideal for placing a lock. The purpose of a synchronization object is to prevent simultaneous access to a resource. Every shared resource should have its own lock, and the best place to use those locks is around the few critical lines where its associated resource is actually used. If you always put the lock around the entire function body then you are likely blocking other threads for longer than necessary, which brings down the overall performance.


Is Lock or Mutex faster?

They serve different purposes.

The lock statement is part of the C# language. Using this keyword cleans up your code and clearly outlines the critical section. According to this answer a lock statement costs at least ~50ns so it's not much to worry about anyways.

On the other hand, a Mutex is an object that can be shared between processes so it is intended to be used for IPC. I don't see any reason to give up the lock syntax in favour of Mutex if you are not using it for IPC.


How is the shared resource part of the code determined?

I'll give an analogy to help you identify shared resources.

Imagine that your threads are workers on a construction site. The site has a portable toilet, and some power tools. Each worker has a different job to do so they grab their respective tools (not shared) and go to work. At some point each of these workers will have to use the toilet. The toilet has a lock to guarantee that only one worker uses it at a time. If the toilet is locked when another worker needs it, then they line up and wait for it to be unlocked.

In this analogy the power tools may be private class variables or objects that only one thread needs to access. While the toilet is an object that more than one thread will have to access at some point. That makes it a shared resource.

Does Visual Studio have the ability to analyze where resources are shared and need to be made thread safe?

Run the code in a debugger and see what breaks! The debugger will help you identify threading problems like deadlocks, and while paused you can see what method each thread is currently executing. If you see two threads working with the same variable then it's a shared resource.


How to make acquired lock thread to release the lock after 2.5 sec and queue all other threads which need the lock?

This question should really be its own post.

If a thread locks something, it is responsible for unlocking it. If the locked section is taking too long then there may just be a problem with your design. Implementing a timer to "cut off" a thread that has a lock is a dangerous design. Instead, you could place "checkpoints" in your thread method that check if it has executed for too long, using a timer started at the beginning of the method. If it needs to exit, it should release the lock and exit the method early so that it no longer accesses shared resources.

Using the lock syntax automatically causes other threads to wait for the lock to be free. If multiple threads need the same lock then the order that they receive the lock is not guaranteed.

Artyom Ignatovich
  • 591
  • 1
  • 3
  • 13
Romen
  • 1,617
  • 10
  • 26
  • Your answer is good.but you mentioned "In this analogy the power tools may be private class variables or objects that only one thread needs to access" i am opposite because private classes variables do not grantee to be no shared if the class be static class.otherwise it's shared. and you have to make it [ThreadStatic] – A Farmanbar Jul 25 '19 at 15:10
  • @Mr.AF Consider that a `private` member of a class can only be accessed by that class. If you design a static class to be accessing its members from multiple threads then of course that would be a case where private members become a shared resource. I used the word *"may"* in that sentence to clarify that it is a *possibility*, not a rule. – Romen Jul 25 '19 at 15:17
1

The response by @romen is useful in discussing the overall ideas. In terms of concretely doing the locking, let's look at a few different situations and solutions. I'm assuming that we are using C# here. In addition I will generally take the perspective of writing a class that needs to use locking within itself to make sure that consistency is preserved.

  1. Thread locking only. In this scenario, you have multiple threads and only want to prevent two different threads from altering the same part of memory (say a double) at the same time, which would result in corrupted memory. You can just use the "lock" statement in C#. HOWEVER, in contemporary programming environments this is not as useful as you might think. The reason is that within a "lock" statement there are a variety of ways of calling back into external code (i.e. code that is outside the class) and this external code may then call back into the lock (possibly asynchronously). In that situation the second time the "lock" statement is encountered, the flow may well pass right into the lock, regardless of the fact that the lock was already obtained. This is often not at all what you want. It will happen whenever the second call on the lock happens to occur on the same thread as the first call. And that can happen quite easily because C# is full of Tasks, which are basically units of work that can execute, block for other Tasks, etc all on a single thread.

  2. Task locking for the purpose of preserving the state consistency of the object. In this scenario, there are a set of private fields within the class that must have a certain invariant relationship to each other both before and after each class method is called. The changes to these variables is done through straight-line code, specifically with no callbacks to code outside of the class and no asynchronous operations. An example would be a concurrent linked list, say, where there is a _count field and also _head and _tail pointers that need to be consistent with the count. In this situation a good approach is to use SemaphoreSlim in a synchronous manner. We can wrap it in a few handy classes like this --

    public struct ActionOnDispose : IDisposable
    {
        public ActionOnDispose(Action action) => this.Action = action;
        private Action Action { get; }
        public void Dispose() => this.Action?.Invoke();
    }

    public class StateLock
    {
        private SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);

        public bool IsLocked => this.Semaphore.CurrentCount == 0;

        public ActionOnDispose Lock()
        {
            this.Semaphore.Wait();
            return new ActionOnDispose(() => this.Semaphore.Release());
        }
    }

The point of the StateLock class is that the only way to use the semaphore is by Wait, and not by WaitAsync. More on this later. Comment: the purpose of ActionOnDispose is to enable statements such as "using (stateLock.Lock()) { … }".

  1. Task-level locking for the purpose of preventing re-entry into methods, where within a lock the methods may invoke user callbacks or other code that is outside the class. This would include all cases where there are asynchronous operations within the lock, such as "await" -- when you await, any other Task may run and call back into your method. In this situation a good approach is to use SemaphoreSlim again but with an asynchronous signature. The following class provides some bells and whistles on what is essentially a call to SemaphoreSlim(1,1).WaitAsync(). You use it in a code construct like "using (await methodLock.LockAsync()) { … }". Comment: the purpose of the helper struct is to prevent accidentally omitting the "await" in the using statement.
    public class MethodLock 
    {
        private SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);

        public bool IsLocked => this.CurrentCount == 0;

        private async Task<ActionOnDispose> RequestLockAsync()
        {
            await this.Semaphore.WaitAsync().ConfigureAwait(false);
            return new ActionOnDispose( () => this.Semaphore.Release());
        }

        public TaskReturningActionOnDispose LockAsync()
        {
            return new TaskReturningActionOnDispose(this.RequestLockAsync());
        }
    }
    public struct TaskReturningActionOnDispose
    {
        private Task<ActionOnDispose> TaskResultingInActionOnDispose { get; }

        public TaskReturningActionOnDispose(Task<ActionOnDispose> task)
        {
            if (task == null) { throw new ArgumentNullException("task"); }
            this.TaskResultingInActionOnDispose = task;
        }

        // Here is the key method, that makes it awaitable.
        public TaskAwaiter<ActionOnDispose> GetAwaiter()
        {
            return this.TaskResultingInActionOnDispose.GetAwaiter();
        }
    }
  1. What you don't want to do is freely mix together both LockAsync() and Lock() on the same SemaphoreSlim. Experience shows that this leads very quickly to a lot of difficult-to-identify deadlocks. On the other hand if you stick to the two classes above you will not have these problems. It is still possible to have deadlocks, for example if within a Lock() you call another class method that also does a Lock(), or if you do a LockAsync() in a method and then the called-back user code tries to re-enter the same method. But preventing those reentry situations is exactly the point of the locks -- deadlocks in these cases are "normal" bugs that represent a logic error in your design and are fairly straightforward to deal with. One tip for this, if you want to easily detect such deadlocks, what you can do is before actually doing the Wait() or WaitAsync() you can first do a preliminary Wait/WaitAsync with a timeout, and if the timeout occurs print a message saying there is likely a deadlock. Obviously you would do this within #if DEBUG / #endif.

  2. Another typical locking situation is when you want some of your Task(s) to wait until a condition is set to true by another Task. For example, you may want to wait until the application is initialized. To accomplish this, use a TaskCompletionSource to create a wait flag as shown in the following class. You could also use ManualResetEventSlim but if you do that then it requires disposal, which is not convenient at all.

    public class Null { private Null() {} } // a reference type whose only possible value is null. 

    public class WaitFlag
    {
        public WaitFlag()
        {
            this._taskCompletionSource = new TaskCompletionSource<Null>(TaskCreationOptions.RunContinuationsAsynchronously);
        }
        public WaitFlag( bool value): this()
        {
            this.Value = value;
        }

        private volatile TaskCompletionSource<Null> _taskCompletionSource;

        public static implicit operator bool(WaitFlag waitFlag) => waitFlag.Value;
        public override string ToString() => ((bool)this).ToString();

        public async Task WaitAsync()
        {
            Task waitTask = this._taskCompletionSource.Task;
            await waitTask;
        }

        public void Set() => this.Value = true;
        public void Reset() => this.Value = false;

        public bool Value {
            get {
                return this._taskCompletionSource.Task.IsCompleted;
            }
            set {
                if (value) { // set
                    this._taskCompletionSource.TrySetResult(null);
                } else { // reset
                    var tcs = this._taskCompletionSource;
                    if (tcs.Task.IsCompleted) {
                        bool didReset = (tcs == Interlocked.CompareExchange(ref this._taskCompletionSource, new TaskCompletionSource<Null>(TaskCreationOptions.RunContinuationsAsynchronously), tcs));
                        Debug.Assert(didReset);
                    }
                }
            }
        }
    }
  1. Lastly, when you want to atomically test and set a flag. This is useful when you want to execute an operation only if it is not already being executed. You could use the StateLock to do this. However a lighter-weight solution for this specific situation is to use the Interlocked class. Personally I find Interlocked code is confusing to read because I can never remember which parameter is being tested and which is being set. To make it into a TestAndSet operation:
    public class InterlockedBoolean
    {
        private int _flag; // 0 means false, 1 means true

        // Sets the flag if it was not already set, and returns the value that the flag had before the operation.
        public bool TestAndSet()
        {
            int ifEqualTo = 0;
            int thenAssignValue = 1;
            int oldValue = Interlocked.CompareExchange(ref this._flag, thenAssignValue, ifEqualTo);
            return oldValue == 1;
        }

        public void Unset()
        {
            int ifEqualTo = 1;
            int thenAssignValue = 0;
            int oldValue = Interlocked.CompareExchange(ref this._flag, thenAssignValue, ifEqualTo);
            if (oldValue != 1) { throw new InvalidOperationException("Flag was already unset."); }
        }
    }

I would like to say that none of the code above is brilliantly original. There are many antecedents to all of them which you can find by searching enough on the internet. Notable authors on this include Toub, Hanselman, Cleary, and others. The "interlocked" part in WaitFlag is based on a post by Toub, I find it a bit mind-bending myself.

Edit: One thing I didn't show above is what to do when, for example, you absolutely have to lock synchronously but the class design requires MethodLock instead of StateLock. What you can do in this case is add a method LockOrThrow to MethodLock which will test the lock and throw an exception if it cannot be obtained after a (very) short timeout. This allows you to lock synchronously while preventing the kinds of problems that would occur if you were mixing Lock and LockAsync freely. Of course it is up to you to make sure that the throw doesn't happen.

Edit: This is to address the specific concepts and questions in the original posting.

(a) How to protect the critical section in the method. Putting the locks in a "using" statement as shown below, you can have multiple tasks calling into the method (or several methods in a class) without any two critical sections executing at the same time.

    public class ThreadSafeClass {
        private StateLock StateLock { get; } = new StateLock();

        public void FirstMethod(string param)
        {
            using (this.StateLock.Lock()) {
                ** critical section **
                // There are a lot of methods calls but not to other locked methods
                // Switch cases, if conditions, dictionary use, etc -- no problem
                // But NOT: await SomethingAsync();
                // and NOT: callbackIntoUserCode();
                ** critical section **  
            }
        }

        public void SecondMethod()
        {
             using (this.StateLock.Lock()) {
                  ** another, possibly different, critical section **
             }
        }
    }

    public class ThreadSafeAsyncClass {
        private MethodLock MethodLock { get; } = new MethodLock();

        public async Task FirstMethodAsync(string param)
        {
            using (await this.MethodLock.LockAsync()) {
                ** critical section **
                await SomethingAsync(); // OK
                callbackIntoUserCode(); // OK
            }
        }

        public async Task SecondMethodAsync()
        {
             using (await this.MethodLock.LockAsync()) {
                  ** another, possibly different, critical section using async **
             }
        }
    }

(b) Could delegates help, given that Delegate is a thread-safe class? Nope. When we say that a class is thread-safe, what it means is that it will successfully execute multiple calls from multiple threads (usually they actually mean Tasks). That's true for Delegate; since none of the data in the Delegate is changeable, it is impossible for that data to become corrupted. What the delegate does is call a method (or block of code) that you have specified. If the delegate is in the process of calling your method, and while it is doing that another thread uses the same delegate to also call your method, then the delegate will successfully call your method for both threads. However, the delegate doesn't do anything to make sure that your method is thread-safe. When the two method calls execute, they could interfere with each other. So even though Delegate is a thread-safe way of calling your method, it does not protect the method. In summary, delegates pretty much never have an effect on thread safety.

(c) The diagram and correct use of the lock. In the diagram, the label for "thread safe section" is not correct. The thread safe section is the section within the lock (within the "using" block in the example above) which in the picture says "Call Method". Another problem with the diagram is it seems to show the same lock being used both around the Call Method on the left, and then also within the Method on the right. The problem with this is that if you lock before you call the method, then when you get into the method and try to lock again, you will not be able to obtain the lock the second time. (Here I am referring to task locks like StateLock and MethodLock; if you were using just the C# "lock" keyword then the second lock would do nothing because you would be invoking it on the same thread as the first lock. But from a design point of view you wouldn't want to do this. In most cases you should lock within the method that contains the critical code, and should not lock outside before calling the method.

(d) Is Lock or Mutex faster. Generally the speed question is hard because it depends on so many factors. But broadly speaking, locks that are effective within a single process, such as SemaphoreSlim, Interlocked, and the "lock" keyword, will have much faster performance than locks that are effective across processes, like Semaphore and Mutex. The Interlocked methods will likely be the fastest.

(e) Identifying shared resources and whether visual studio can identify them automatically. This is pretty much intrinsic to the challenge of designing good software. However if you take the approach of wrapping your resources in thread-safe classes then there will be no risk of any code accessing those resources other than through the class. That way, you do not have to search all over your code base to see where the resource is accessed and protect those accesses with locks.

(f) How to release a lock after 2.5 seconds and queue other requests to access the lock. I can think of a couple of ways to interpret this question. If all you want to do is make other requests wait until the lock is released, and in the lock you want to do something that takes 2.5 seconds, then you don't have to do anything special. For example in the ThreadSafeAsyncClass above, you could simply put "await Task.Delay( Timespan.FromSeconds(2.5))" inside the "using" block in FirstMethodAsync. When one task is executing "await FirstMethodAsync("")" then other tasks will wait for the completion of the first task, which will take about 2.5 seconds. On the other hand if what you want to do is have a producer-consumer queue then what you should do is use the approach described in StateLock; the producer should obtain the lock just briefly while it is putting something into the queue, and the consumer should also obtain the lock briefly while it is taking something off the other end of the queue.

sjb-sjb
  • 1,112
  • 6
  • 14
  • P.S. I did not discuss inter-process locking in my answer. For inter-process locking you can use a Semaphore instead of SemaphoreSlim and pass in a name to the constructor to create a named system semaphore that is visible across processes. Obviously that kind of synchronization is slower. – sjb-sjb Jul 25 '19 at 04:05
  • I am reading your answer.but edit your answer and address concepts listed in question.i think you focused on a single topic .is it ture? – A Farmanbar Jul 25 '19 at 14:29
  • See the edits above. You're really making me work for my 500 points :-). I don't think I'm focusing on a single topic, this covers a range -- – sjb-sjb Jul 25 '19 at 23:29
  • Well, I like my code and I think it's pretty good. I have tried similar code, and you are welcome to try it. That's pretty much all the guarantee that you can get for free on StackOverflow !! – sjb-sjb Jul 25 '19 at 23:49
  • ok i will try ,if it works you will be accepted. – A Farmanbar Jul 26 '19 at 00:01
  • i did test it . it's much more slower because of time overhead of using tasks and also i had to change alot in main code. and also it had some syntax errors – A Farmanbar Jul 26 '19 at 14:53
  • Also what kind of locking did you have before? Any type of locking is going to be a lot slower than no locking at all. But that might be the price of having it work correctly instead of fail badly. – sjb-sjb Jul 26 '19 at 17:45
  • Works though? Slower than what? Might need to keep your changes and then just comment/uncomment the Wait() statement, in order to get an apples-to-apples comparison. – sjb-sjb Jul 26 '19 at 18:28
0

Here is an example. The _sharedString may be accessed by two functions, MethodAdd and MethodDelete which may be called from different threads. To ensure that access to the _sharedString is serialized, that is, one thread at a time, we typically create a lock object, and then use the C# lock keyword to get exclusive access to the shared resource, in this case _sharedString.

private static object _lock = new object();

private string _sharedString = "";

public void MethodAdd(string s)
{
    lock(_lock)
    {
       // Exclusive access to _sharedString.
        _sharedString = _sharedString + s;
    }
}

public void MethodDelete(int n)
{
    lock (_lock)
    {
       // Exclusive access to _sharedString.
        _sharedString = _sharedString.Substring(0, n);
    }
}

You mention in your question By thread safe I mean that I want multiple concurrent operations - whereby none of them will be blocking each other, but this is impossible to achieve. There will always be a certain amount of blocking in order to achieve thread-safety. If your server is becoming too slow because of lock (which you did not mention in your question, but in comments only), then you should revise your design; your shared resource is the bottleneck.

RobertBaron
  • 2,817
  • 1
  • 12
  • 19
  • @Mr.AF - `Monitor.TryEnter()` will queue thread only for the specified amount of time. If thread gets the lock during that time, it executes the code. Otherwise, it does not. `Monitor.Enter()` (which is equivalent to the `lock` keyword), queues the thread and wait for the lock. – RobertBaron Jul 22 '19 at 17:32
  • "will queue thread only for the specified amount of time" . how we can manage timeout ? – A Farmanbar Jul 22 '19 at 17:58
  • 1
    @Mr.AF - Not sure I understand your question. Normally, you want the code to execute, and should use `Monitor.Enter()` or more simply `lock`. If you are willing to timeout after 3 secs, like in your example, then I do not know how to handle that timeout. It really depends on what your code is trying to do. – RobertBaron Jul 22 '19 at 18:03
  • @RoberBaron see edit . – A Farmanbar Jul 22 '19 at 18:13
  • @Mr.AF - To tell a thread to abort, you have to use a variable to communicate with the thread. The thread periodically checks the state of the variable, and if the variable says to abort, it aborts. Of course, the variable is set in another thread. – RobertBaron Jul 22 '19 at 20:22
  • i might start bounty.and next update will be added . – A Farmanbar Jul 23 '19 at 11:22
  • @Mr.AF - Good idea. Your question is really the section **I need this solution**. – RobertBaron Jul 23 '19 at 11:26
  • 1
    @Mr.AF - I have been looking at all these answers and your comments, and it may be that none of them will completely solve your problem. If you are looking at thousands or more threads that need to access a shared resource on a single server at the same time, then no matter how big and fast you make your server, it has limits. You may have to redesign your solution to run on multiple servers. Then your solution would scale. – RobertBaron Jul 26 '19 at 20:00