Resource locking with async/await
Asked Answered
S

2

32

I have an application where I have a shared resource (a Motion system) which can be accessed by multiple clients. I have individual Operations that require access to the system for the duration of the move and which should throw 'Busy' exceptions if conflicting operations are requested at the same time. I also have Sequencers which need to acquire exclusive access to the Motion system for the execution of several Operations, interspersed with other actions; during the entire sequence, no other clients should be able to run Operations.

I've traditionally approached this using Thread-affinity, so that a Thread can request exclusive access and run blocking calls corresponding to operations. While the Thread has access, no other Threads may use the resource. The problem I'm having now is that I've moved toward implementing my system using async/await patterns, to allow cleaner sequencer implementation. The problem is that now my sequencer is not always running on the same thread; the active thread can change during the course of callbacks, so it is no longer easy to determine whether I am in a valid context to keep running operations. One item of note is that some of the Operations themselves are composed of awaits, which means both sequences and individual Operations can span multiple threads.

My question: does anybody know of a good pattern to deal with acquiring exclusive access in the presence of thread switching due to async/await?

For reference, a few things I've considered:

  1. I could create a custom SynchronizationContext that marshals all sequencer calls for the duration of a sequence back to a single Thread. This has the benefit of allowing me to reuse my existing thread-affinity access management code. The downside is that this will require dedicating a Thread whenever I do either a Sequence or an Operation (since Operations can also span multiple threads.)

  2. Create an acquirable access token to pass to the Operation methods to prove that you have acquired access. This has the downside of bloating the methods with a token parameter.

  3. Use the access token approach from (2), but create a duplicate interface implementation for the Operations interface so a wrapper can be instantiated with the token 'baked-in'. This creates some ugly glue code, but it cleans up the sequencer code so that it no longer needs to pass a token to each method.

Screw answered 2/10, 2012 at 16:43 Comment(0)
F
24

My question: does anybody know of a good pattern to deal with acquiring exclusive access in the presence of thread switching due to async/await?

Yes, you can use AsyncLock, which is also available as part of my AsyncEx library. If you want to have a "TryLock" kind of operation, then you may have to create your own primitive.

You do lose some of the capability to do safety checks: there is no way to check whether the currently-executing thread has a specific AsyncLock.

Other options include ConcurrentExclusiveSchedulerPair (which I blog about here) or TPL Dataflow.

Foreordination answered 2/10, 2012 at 17:5 Comment(1)
I'll mark this as the answer for the helpful reading. I ultimately ended up creating proxies for the interfaces that include access checks. I can then hand out a special disposable 'exclusive access' proxy that prevents the other proxy instances from using the system while it's alive.Screw
G
12

There's SemaphoreSlim.WaitAsync which fits closely here. (I found it in a similar question).

Goblin answered 16/11, 2015 at 13:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.