-1

Is this possible to lock method for one thread and force another to go futher rather than waiting until first thread finish? Can this problem be resolved with static thread or some proper pattern with one instance of mendtioned below service.

For presentation purposes, it can be done with static boolen like below.

public class SomeService
{
  private readonly IRepository _repo;
  public SomeService(IRepository repo)
  {
    _repo = repo;
  }

  private Thread threadOne;

  public static bool isLocked { get; set; }

  public void StartSomeMethod()
  {
    if(!isLocked)
    {
      threadOne = new Thread(SomeMethod);
      isLocked = true;
    }
  }

  public void SomeMethod()
  {
    while(true)
    {
      lots of time
    }
    ...
    isLocked = false;
  }
}

I want to avoid situation when user clicked, by accident, two times to start and accidentailly second thread starts immediatelly after first finished.

gorrch
  • 521
  • 3
  • 16
  • Possible duplicate of [Is there a "try to lock, skip if timed out" operation in C#?](https://stackoverflow.com/questions/8546/is-there-a-try-to-lock-skip-if-timed-out-operation-in-c) – mjwills Dec 19 '18 at 10:03
  • *"user clicked, by accident, two times"* - you can simply prevent this possibility, e.g. by disabling the button. Another possibility is to rethink your design. One thing is creating working thread in the constructor or by using some form of init/deinit pattern to ensure its single-ability, and totally another (bad) idea to create it in some method, which can be called from anywhere. Why don't you create a new instance of `SomeService` instead of trying to reuse one for multiple cases? – Sinatr Dec 19 '18 at 10:17
  • I do not want to put working thread inside constructor because with injecting service into controller it instantly create thread which is more load for application, I suppose. By that line of thinking I could create static thread with parameterless constructor and then somehow check state of this thread. Please correct me because I can write rubbish. – gorrch Dec 19 '18 at 14:40
  • 1
    Should not this `object locker` be static, because of another threads? – gorrch Dec 19 '18 at 21:07
  • Generally speaking, yes it should be `static`. – mjwills Dec 19 '18 at 21:48
  • Second thing is I did not mention about `SomeMethod()` is `async` so on `Monitor.Exit` I get: `System.Threading.SynchronizationLockException`: 'Object synchronization method was called from an unsynchronized block of code.' Obviously this is my fault, but none answer is correct for me. :) – gorrch Dec 19 '18 at 22:17

3 Answers3

1

You can use lock :)

object locker = new object();
void MethodToLockForAThread()
{
    lock(locker)
    {
      //put method body here
    }
}

Now the result will be that when this method is called by a thread (any thread) it puts something like flag at the beginning of lock: "STOP! You are not allowed to go any further, you must wait!" Like red light on crossroads. When thread that called this method first, levaes the scope, then at the beginning of the scope this "red light" changes into green.

If you want to not call the method when it is already called by another thread, the only way to do this is by using bool value. For example:

object locker = new object();
bool canAccess = true;
void MethodToLockForAThread()
{
    if(!canAccess)
      return;

    lock(locker)
    {
      if(!canAccess)
        return;

      canAccess = false;            

      //put method body here

      canAccess = true;
    }
}

Other check of canAccess in lock scope is because of what has been told on comments. No it's really thread safe. This is kind of protection that is advisible in thread safe singleton.

EDIT

After some discussion with mjwills I have to change my mind and turn more into Monitor.TryEnter. You can use it like that:

object locker = new object();
void ThreadMethod()
{
  if(Monitor.TryEnter(locker, TimeSpan.FromMiliseconds(1))
  {
     try
     {
       //do the thread code
     }
     finally
     {
       Monitor.Exit(locker);
     }
  } else
    return; //means that the lock has not been aquired
}

Now, lock could not be aquired because of some exception or because some other thread has already acuired it. In second parameter you can pass the time that a thread will wait to acquire a lock. I gave here short time because you don't want the other thread to do the job, when first is doing it. So this solution seems the best.

When the other thread could not acquire the lock, it will go further instead of waiting (well it will wait for 1 milisecond).

Servy
  • 202,030
  • 26
  • 332
  • 449
Adam Jachocki
  • 1,897
  • 1
  • 12
  • 28
  • Ok, but I do not want that another thread waits. I mean, if it is locked it should omit this method. – gorrch Dec 19 '18 at 09:49
  • Ok, take a look at modified version of my post. If you want to no to start a thread at all when another is running, you can do similar, but check canAccess before creating the thread. Remember that canAccess should be set only in lock – Adam Jachocki Dec 19 '18 at 09:55
0

You can use a AutoResetEvent instead of your isLocked flag.

AutoResetEvent autoResetEvent = new AutoResetEvent(true);
public void StartSomeMethod()
{
    if(autoResetEvent.WaitOne(0))
    {
        //start thread
    }
}

public void SomeMethod()
{
    try
    {
        //Do your work
    }
    finally
    {
        autoResetEvent.Set();
    }
}
nosale
  • 808
  • 6
  • 14
0

Since lock is a language-specific wrapper around Monitor class, you need Monitor.TryEnter:

public class SomeService
{
    private readonly object lockObject = new object();

    public void StartSomeMethod()
    {
        if (Monitor.TryEnter(lockObject))
        {
            // start new thread
        }
    }

    public void SomeMethod()
    {
        try
        {
            // ...
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }    
}
Dennis
  • 37,026
  • 10
  • 82
  • 150
  • Your answer is most correct, but do you know how to do it if `SomeMethod` is `async`? – gorrch Dec 19 '18 at 22:25
  • 1
    @gorrch: I usually use this library for `async` coordination: https://github.com/StephenCleary/AsyncEx/ . In your particular case I'd use `AsyncManualResetEvent` (see "Advanced Usage" section), in despite of there are `AsyncLock` and `AsyncMonitor`. – Dennis Dec 20 '18 at 05:54