Home > OS >  How can I test if an object is locked, and if not use it within a task about to be called?
How can I test if an object is locked, and if not use it within a task about to be called?

Time:11-16

I need to do something like the following code. Note, this does not work due to the nature of locks in .Net (which I'll address after the code) but I need some form of implementation like this that works, but I don't know what to do.

static object lockMethod = new object();
static object lockTask = new object();

public static string testLocksAndTasks()
{
  //This method is fast executing code but to prevent other issues, I
  //lock this method.  This might be impertinent to the base need of this 
  //question, but since I do use a lock on the entire requesting method, I 
  //put it here.  This is because I integrate some logging and a bit of 
  //other logic on static variables so I just lock the method.  This entire 
  //function is fast.  The really slow code is in 
  //DoWorkInOtherThreadMethod(), which is why it needs to run in a 
  //background task.
  lock (lockMethod)
  {
    if (Monitor.TryEnter(lockTask))
    {
      Task.Run(DoWorkInOtherThreadMethod)
        .ContinueWith(ct=>
          Monitor.Exit(lockTask)); //Oop! Unlocking on separate task NOT ALLOWED!

      return "We locked lockTask and started the thread!";
    }
    else return "Task called and executed by other request!";
  }
}

The above code gets the point across for what I'm trying to accomplish. I need to have a method (that is executed from a web request) and I need to test if a launched task from the request is already running in another thread. If it isn't running, I need to create the lock for the task (lockTask in this example) and prevent future calls while it runs, and report ack to the caller the state of the background task. If the lockTask is already in use, I specifically need to return "Task X is already running for Y item". (Note, I didn't include that extra information for what's already running, but that's not difficult to do and not required for this example.)

My code could theoretically work because once the entire task is finished, the call to ContinueWith() would unlock the lock for the task. However, what I've found is that this throws errors because the initial lock on lockTask is created in one thread, and then the subsequent unlock with Monitor.Exit(lockTask) is occurring on another thread and that's not allowed.

I could try to restructure my code to some thing like the code provided below, but this also has issues for my needs.

public static string testLocksAndTasks()
{
  lock (lockMethod)
  {
    //Check if we're locked!
    if (!Monitor.IsEntered(lockTask))
    {
      Task.Run(()=>
        {
          //We weren't locked, so TryEnter...
          if (Monitor.TryEnter(lockTask))
          {
            DoWorkInOtherThreadMethod();
            Monitor.Exit(lockTask);  //NOTE: I KNOW THIS SHOULD BE WRAPPED IN A 
                                     //TRY/CATCH/FINALLY.  I'm just keeping sample 
                                     //the code simple.
          }
          else
          {
            //Oh no!  This is actually quite possible, but this is a case I never 
            //want to reach?!  I can't report this back to the initial call to 
            //testLocksAndTasks since we are in a new thread!
          }
        });

      return "We locked lockTask and started the thread!";
    }
    else return "Task called and executed by other request!";
  }
}

My comment above outlines the obvious problem. I can create my task lock within the task and this fixes the first issue with the lock and exits happening on separate threads. However, this introduces a different but still important issue.

Since this code can execute semi-simultaneously across multiple request threads, there is a chance that there might be multiple calls to !Monitor.IsEntered(lockTask) returning true because the lock is not set until the Monitor.TryEnter(...) request is made within a new task. This wouldn't be an issue, however, I now can't return a proper state response to testLocksAndTasks().

How can I properly implement something like this where I lock a long running task but can also report if it's running?

CodePudding user response:

Sounds like you don't need a lock but a simple flag indicating whether task is running. Now to read and set this flag you can use regular lock. So you check the flag and start the task if necessary inside the lock. When task is done you again set the flag inside the lock. Sample code:

static object lockTask = new object();
private static bool taskRunning = false;
public static string testLocksAndTasks()
{
    lock (lockTask)
    {
        // check inside the lock
        if (!taskRunning)
        {
            Task.Run(DoWorkInOtherThreadMethod)
                .ContinueWith(ct =>
                {
                    lock (lockTask)
                    {
                        // reset flag inside the lock
                        taskRunning = false;
                    }
                });
            // start task and set the flag
            taskRunning = true;
            return "We locked lockTask and started the thread!";
        }
        else return "Task called and executed by other request!";
    }
}
  • Related