Understanding CriticalSections

I’ve never used any of the code protection classes (Semaphore, CriticalSection, etc) before but I think that’s what I need to do except I don’t really understand how to use them (yes I’ve read the documentation).

I’m writing a canvas-based code editor. The editor has a LineManager class that tracks the start offsets and lengths of all the lines in the editor.

I think it would be most sensible to run my syntax highlighting code (complex regular expressions) in a separate thread.

The thread that’s processing the lines needs to protect the LineManager property of the editor from being altered until it has finished highlighting otherwise things will break. Does this seem the correct use case for a CriticalSection? If so, how on earth do I implement it. I don’t know how to mark the LineManager property as the critical section.

Help!

Create a new CriticalSection and store it as a property of the window, then:

' wait for the other thread to leave the CriticalSection, then enter it
Do Until myCriticalSection.TryEnter()
   Thread.YieldToNext()
Loop

Try
   ' work goes here 
Finally
   myCriticalSection.Leave()
End Try

As long as you’re doing this on both sides (the GUI side and the Thread side), each side will wait for the other to Leave the CriticalSection before doing its work.

The Try...Finally...End Try construct guarantees that CriticalSection.Leave() is called even if an exception is raised, but does not catch or handle the exception.

2 Likes

Hmm, so I just create a CriticalSection property in the LineManager class on the canvas and then yield to it from within the canvas and the highlighter thread?

That’s pretty much it, yeah. Just remember that CriticalSection is a class, so you have to instantiate it with the New keyword before trying to use it.

1 Like

However whatever you do, do NOT use a CriticalSection on the main thread. If you enter a critical section on main, and your thread already has the lock, your app hangs. The main thread will wait for the other thread, but the event loop will wait for the main thread and never give time to the other thread. So they’ll both sit there waiting for each other forever.

I don’t believe this is intended behavior, otherwise it’d be documented, but it was reported years ago and not fixed so… it might as well be intended behavior at this point.

That seems concerning but also how else would I use it?

I’ve been examining the source code for Alex Restrepo’s CustomEditfield (as he uses a separate thread to tokenise the lines managed by the canvas). The trouble is the code base is enormous and there are several levels of indirection which makes tracing the highlighting code challenging.

The LineManager is a class owned by the canvas (which itself is on the main thread - it has to be as it is a UI component). I need to lock this when the highlighter thread is running but the canvas will also need to lock the line manager when it is accepting text input.

I don’t see a way to not use a CriticalSection on the main thread in this case. Unless of course I’m misunderstanding.

Instead of using a critical section could you not clone the line manager data before starting the highlighter thread? It would mean that the data was stale if the user performed editing tasks while the thread was running but I guess you will have to restart the highlighter again in this scenario anyway.

If that code segment is really a critical section, a segment of code doing something fast and “low level”, isolated from other compatible segments touching those critical parts; in the main thread, if such code were surrounded by pragmas disallowing background tasks to avoid reentrancy, it in theory should be safe of creating such self deadlocks.

Lots of flags can be used for lots of interprocs intercomm, locking the access while reading/writing those flags for a microsecond usually is the only part that needs locking schemes.

Sometimes you need to be more careful in other languages than in Xojo, because in other languages you can be interrupted between simple instructions, and in Xojo just in “cooperative checkpoints” the compiler sets in.

So lets imagine a flag “busy”, to signalize others to wait, but don’t lock, they can continue doing something else.

And you (and other processes conflicting processes) want to set, you may want to create a function to try to set this condition like:

Function SetBusy As Boolean    // true if you got it, false means it's already busy elsewhere
  Var got As Boolean = False
  critical.Enter               // don't interrupt while touching that flag 
  If not sharedflags.busy Then
    sharedflags.busy = True
    got = True
  End
  critical.Leave
  Return got
End

Var processed As Boolean = False

Do
  If SetBusy Then
    LongProcess()
    processed = True
  End
Until processed
ClearBusy()

True, but you still need to use TryEnter to determine if it’s safe to continue. That’s safe, but you need to decide what to do if it returns false. Disabling background tasks only ensures other threads won’t get time, not that they don’t have the lock.

1 Like

It does limit the utility doesn’t it? In my case, I’m using it with SQLite. In WAL mode, you can have as many reader connections as you like, but only one connection can write at a time. So each thread, including main, gets its own connection. The non-main threads will use a shared CriticalSection around their transactions to coordinate writes. The main thread is never allowed to write, so doesn’t need or bother with the CriticalSection at all. In fact, I wrote my subclass to fire an exception if I try to start a transaction on the main thread.

1 Like

FWIW, we tried a number of techniques including CriticalSection and pragmas, and finally settled on Semaphore.

We also use a class I call SemaphoreHolder to make sure the flag is released no matter how the function that entered it ends, be it Exception or early return.

The concept relies on the fact that an object’s Destructor will fire as soon as it goes out of scope, so our code looks something like this.

var s as SemaphoreHolder = SemaphoreHolder.Enter( MySemaphoreProp )

// Other code

s = nil // not really needed, but will keep the compiler from complaining

The Shared method will enter the Semaphore using TrySignal in a loop, as mentioned above, then return a new instance with the Semaphore attached. That class’s Destructor will call Leave. When s goes out of score in the example above, the Semaphore will be released automatically.

1 Like