2

I have a simple WebApi controller method that's purpose is to trigger some background processing and return a 202 Accepted response immediately (without necessarily having completed the background processing as is consistent with a 202 response.)

public async Task<IHttpActionResult> DoSomething(string id)
{
    HostingEnvironment.QueueBackgroundWorkItem(async ct =>
    {
        //Do work
    }
    return ResponseMessage(new HttpResponseMessage(HttpStatusCode.Accepted));
}

However, I want to be able to prevent multiple requests to the same endpoint with the same id from triggering multiple instances of the same background processing simultaneously.

Effectively, if two requests with the same id's were to be made at the same time (or near enough), the first one would do the processing for that id and the second one would be able to identify this and take action accordingly and not duplicate the work that's already being done.

I'd like to avoid databases/persistent storage if at all possible and I'm aware of the risks of running async tasks within an IIS worker process - for the sake of the argument, the background processing is not critical.

How can I go about doing this? Any help would be much appreciated.

James Law
  • 6,067
  • 4
  • 36
  • 49

1 Answers1

0

You'll need some kind of storage shared between all your possible workers. This could be, for example:

  • A static variable. Probably the easiest to implement, but has limitations when the application does not run in only one AppDomain (especially important if you want to scale). So probably not the best way
  • An SQL Database: Probably the most common one. If your application already uses one, I'd say go for this route.
  • A No-SQL database, for example a key-value store. Might be an alternative if your application does not use a SQL database yet.
  • Some external component such a workflow management tool

EDIT: Example using ConcurrentDictionary (per request of the thread starter - I stil think using a (possibly NoSQL) database would be the most elegant way) - actually you could just put Tasks into the dictionary:

private ConcurrentDictionary<string, Task<SomeType>> _cache = new //...

var task = _cache.GetOrAdd("<Key>", key => Task.Run(() => /*do some work*/));
if (task.IsCompleted)
   /*result ready*/;
else
   /*have to wait*/;
Matthias
  • 12,053
  • 4
  • 49
  • 91
  • A really easy way is to create a folder and use lock files. – Matthew Whited Jan 07 '16 at 18:33
  • I've looked at implementing a static collection to keep track of what's running but in order to maintain thread safety I'd need to acquire the locks for the duration of the background processing which would impede performance. Not that I can figure out how to lock individual items in a collection anyway! – James Law Jan 07 '16 at 18:35
  • Why's that? You only need to lock the collection while doing inserts / lookups. See this question for some FCL classes http://stackoverflow.com/questions/18922985/concurrent-hashsett-in-net-framework which already implement the neccessary locking. – Matthias Jan 07 '16 at 18:36
  • External resources give a really nice way of locking. You can also use system level resource locks such as Mutexes. – Matthew Whited Jan 07 '16 at 18:36
  • @Matthias I probably should have added the additional requirement, I wanted to keep a log of when the processing was last executed and effectively cache for x minutes before allowing it to execute again. The penny just dropped and I basically need two collections - one to keep track of the current processing and one to keep track of past processing. – James Law Jan 07 '16 at 18:42
  • You could also use just one collection and store a placeholder while the processing is still in progress. Otherwise, you can't use the built-in collections as you need to lock them **both** when doing any modification to one of them. – Matthias Jan 07 '16 at 18:43
  • @MatthewWhited That would only work in a single app domain context, in which case a static variable would be easier. Creating a lock file would be a pretty hacky solution. – mason Jan 07 '16 at 18:46
  • @Matthias that's what I meant when I said I couldn't figure out how to lock individual items within a collection - could you provide a code example of how this might work please as that's about the closest I've got in my mind to something that could work. – James Law Jan 07 '16 at 18:50
  • @mason, I guess I could have been more specific and said use a named mutex. – Matthew Whited Jan 07 '16 at 18:53
  • And yes, a lock file would be "hacky" ... but it's less effort then standing up a database instance you don't want or need just for locking and still works across not only processes but systems. – Matthew Whited Jan 07 '16 at 18:53
  • BTW, if you really wana get crazy you could use mscoree to create a cross appdomain static variable. It's really not recomended and I'm not sure how happy it would make the IIS ASP.Net trust model but it is possible. – Matthew Whited Jan 07 '16 at 19:03
  • @MatthewWhited Why would you do that? Using a key-value store or even an embedded database would be much cleaner IMHO. – Matthias Jan 07 '16 at 19:05
  • Than lock files... coding wise maybe but not really as you still need to release the lock. But if you don't already have a database then you are increasing your deployment and footprint. But OP said he wanted a lock so the DB would be a great way to pull that off. – Matthew Whited Jan 07 '16 at 19:06
  • @Matthias thanks for that - however I'm still not sure how you would store and subsequently query when the processing was last completed from that model? – James Law Jan 07 '16 at 19:07
  • You can store that in the SomeType used in the example. Maybe create a wrapper containg your actual data and a DateTime containing the last successful run. – Matthias Jan 07 '16 at 19:08