Introduction
Boolean flags are commonly used to disable a block of code while another is running: for example,
private bool in_use; private void Process() { in_use = true; (...) in_use = false; } private void OnEvent() { if (in_use) return; (...) }
Private InUse As Boolean; Private Sub Process() InUse = True (...) InUse = False End Sub Private Sub Process() If InUse Then Return (...) End Sub
This design has a major drawback :
in_use
blocks cannot be nested, since nesting two such blocks will turn in_use
to false
too early. In the following excerpt, the (...)
section in the Process
function will not run correctly, because in_use
will have been set to false
when it runs.
private void Process() { in_use = true; Action1(); Action2(); (...) // in_use == false here (!) in_use = false; } private void Action1() { in_use = true; (...) in_use = false; } private void Action2() { in_use = true; (...) in_use = false; }
Private Sub Process() InUse = True Action1() Action2() (...) ' InUse = False here (!) InUse = False End Sub Private Sub Action1() InUse = True (...) InUse = False End Sub Private Sub Action2() InUse = True (...) InUse = False End Sub
Such errors are difficult to spot in large applications, and often lead to hard-to-track bugs.
Robust boolean flags
A useful trick in such cases is to replace your boolean flags with boolean properties, linked to a counter : every time you set in_use
to true
, the counter is incremented; every time you set it to false
, the counter is decremented. Retrieving in_use
returns true
when the counter is greater than 0, and false
otherwise. That’s somewhat similar to the way semaphores work in parallel programming.
using System.Diagnostics; private int users_count = 0; public bool in_use { get { return users_count > 0; } set { users_count += (value ? 1 : -1); Debug.Assert(users_count >= 0); } }
Imports System.Diagnostics Private UsersCount As Integer Public Property InUse As Boolean Get Return UsersCount > 0 End Get Set(value As Boolean) UsersCount += If(value, 1, -1) Debug.Assert(UsersCount >= 0) End Set End Property
You can now nest in_use
blocks safely.
Going further
The previous construct is not thread-safe, and might end up in an inconsistent state if an exception occurs in the body of an in_use
block (in_use
will never be set to false
if an exception occurs in an in_use
block).
We solve both problems by declaring a Flag
class which allows for the following construct:
private void Process() { using (Flag flag = new Flag("custom name")) { (...) } } private void OnEvent() { if (Flag.InUse("custom name")) return; (...) }
Private Sub Process() Using flag As New Flag("custom name") (...) End Using End Sub Private Sub OnEvent() If Flag.InUse("custom name") Then Return (...) End Sub
Here is the implementation; the Flag class exposes one static method, InUse(string)
, which returns whether determine whether a resource designated by a string is in use. The Flag
constructor registers a new user by incrementing the flag_users
counter, while the destructor accordingly decrements the flag_users
counter.
class Flag : IDisposable { string name; private static Dictionary<string, int> flag_users = new Dictionary<string,int>(); public static bool InUse(string name) { lock (flag_users) return (flag_users.ContainsKey(name) && flag_users[name] > 0); } public Flag(string name) { this.name = name; lock (flag_users) { if (!flag_users.ContainsKey(name)) flag_users.Add(name, 0); flag_users[name] += 1; } } public void Dispose() { lock (flag_users) flag_users[name] -= 1; } }
Class Flag Implements IDisposable Private Name As String Private Shared FlagUsersCount As New Dictionary(Of String, Integer)() Public Shared Function InUse(name As String) As Boolean SyncLock FlagUsersCount Return (FlagUsersCount.ContainsKey(name) AndAlso FlagUsersCount(name) > 0) End SyncLock End Function Public Sub New(Name As String) Me.Name = Name SyncLock FlagUsersCount If Not FlagUsersCount.ContainsKey(Name) Then FlagUsersCount.Add(Name, 0) End If FlagUsersCount(Name) += 1 End SyncLock End Sub Public Sub Dispose() Implements System.IDisposable.Dispose SyncLock FlagUsersCount FlagUsersCount(Name) -= 1 End SyncLock End Sub End Class
How do you implement your own boolean flags? Post examples in the comments!