简体   繁体   中英

Single Instance only .NET Web Service

I have a particular asmx WebMethod that I want to limit to one running instance at a time.

The reason is that it reads data from database, reads data from a file, process the data, sends emails, and then stores data back to the database and I'm concerned that if two instances are running at one time it could cause problems.

Is there a way to enforce this?

[WebMethod]
public List<string> MyMethod()
{
    using (myEntities context = new myEntities())
    {
        //read database, files and do other stuff here
        context.SaveChanges();
    }
}

Or maybe I can force a lock on the database so that only one thread can be inside the using statement?

Current Solution:

[WebMethod]
public List<string> MyMethod()
{
    List<string> log = new List<string>();
    if(!Monitor.TryEnter(_syncRoot)) {
        log.Add("Proccess Currently Running");
        return log;
    }
    try
    {
        using (myEntities context = new myEntities())
        {
            //doStuff
            context.SaveChanges();
        }
        log.Add("Success"); 
    }catch (Exception ex) {
        log.Add(ex.Message);
    } finally {
        Monitor.Exit(_syncRoot);
    }
    return log;
}

Note:

My current solution seems inadequate for the case where I have multiple servers running, which I do. Maybe obtain a table lock on the table, and if I'm unable to obtain the lock, throw an exception. Can I do this?

At the simplest level you can just make use of lock() on a static object within your webserver. This will ensure that the second request is queued, but will not stop it from running.

public static object _syncRoot = new object();
[WebMethod]
public List<string> MyMethod()
{
    lock (_syncRoot) {
    using (myEntities context = new myEntities())
        {
          //read database, files and do other stuff here
          context.SaveChanges();
        }
    }
}

The next level of complexity is to try and obtain a lock using Monitor.TryEnter , timeout and display an error page.

[WebMethod]
public List<string> MyMethod()
{   
    if (!Monitor.TryEnter(_syncRoot, 5000)) {
        throw new TimeoutExpiredException();
    }
    try {
        // ...
    } finally {
        // Exit the lock if it was obtained
        Monitor.Exit(_syncRoot);
    }
}

It's important to note that this will only work on a single instance webserver - if you're using a cluster, the static object won't be shared between web requests and this won't work. You'll need to use something in the database in this case.

Alternatively, you store a flag in the database when you begin the process the first time, and for all others, it can exit immediately like:

    [WebMethod]
    public List<string> MyMethod()
    {
        using (myEntities context = new myEntities())
        {
              if (/* check if flag set in db table */) {
                   return null; //exit and don't allow anyone else to use
              }

              //read database, files and do other stuff here

              //set flag to false
              context.SaveChanges();
        }
    }

This may be beneficial because it also allows for an external process to also trigger the same action, and can then prevent the UI from doing anything.

I ended up using a transaction with a setting of Serializable.

When I ran this multiple times simultaneously, I got a mix of:

  • the process falling into both exception handlers
  • the process waiting and executing after

Either way, two processes did not run at the same time leaving my database intact :)

[WebMethod]
    public List<string> MyMethod()
    {
        var transactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.Serializable, Timeout = TimeSpan.MaxValue };
        try
        {
            using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
            {
                try
                {
                    using (myEntities context = new myEntities())
                    {
                    //doStuff
                    context.SaveChanges();
                    }
                }catch (Exception ex) {
                    //handle database exception here
                }
            }
        } catch(Exception ex){
            //handle transaction scope exception here
        }
        return new List<string>();
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM