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!