I’m using an invocation described in this question: Synchronization accross threads / atomic checks?
I need to create an method invoker that any thread can call, which will execute on the main executing thread at a specific given point in its execution.
I ended up using this implementation of the Invoker class:
I am aware that is might not be the most efficient in terms of locking, but it’s theoretically working in a similar enough way than Thread.MemoryBarrier(), such as the one SLaks suggested.
EDIT: With MRAB’s suggestions.
public class Invoker
{
private Queue<Action> Actions { get; set; }
public Invoker()
{
this.Actions = new Queue<Action>();
}
public void Execute()
{
Console.WriteLine("Executing {0} actions on thread {1}", this.Actions.Count, Thread.CurrentThread.ManagedThreadId);
while (this.Actions.Count > 0)
{
Action action;
lock (this.Actions)
{
action = this.Actions.Dequeue();
}
action();
}
Console.WriteLine("Executed, {0} actions left", this.Actions.Count);
}
public void Invoke(Action action, bool block = true)
{
if (block)
{
Console.WriteLine("Invoking");
SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);
lock (this.Actions)
{
this.Actions.Enqueue(delegate
{
try
{
action();
Console.WriteLine("Actioned");
}
catch
{
Console.WriteLine("Exception thrown by action");
throw;
}
finally
{
semaphore.Release();
Console.WriteLine("Released");
}
});
}
Console.WriteLine("Enqueued");
Console.WriteLine("Waiting on thread {0}", Thread.CurrentThread.ManagedThreadId);
semaphore.Wait();
Console.WriteLine("Waited");
semaphore.Dispose();
}
else
{
this.Actions.Enqueue(action);
}
}
}
The many Console.WriteLine are there to help me track my freezing, which happens regardless of whether or not this logging is present (i.e., they are not responsible of the freezing and can be discarded as culprits).
The freezing occurs in a scenario where:
- An execution thread runs in a loop (calling
Invoker.Execute). - On 2 other threads, 2 methods are invoked relatively simultaneously (calling
Invoker.Invoke). - The first method works and gets invoked fine, but the second one freezes after “Waiting”, that is, after
semaphore.Wait().
Example output:
Executing 0 actions on thread 1
Executed, 0 actions left
Executing 0 actions on thread 1
Executed, 0 actions left
Invoking
Enqueued
Waiting on thread 7
Executing 1 actions on thread 1
Actioned
Released
Executed, 0 actions left
Waited
Invoking
Enqueued
Waiting on thread 8
What I suspect to be happening is that the execution thread somehow blocks, hence not executing the second enqueued action, and not releasing the semaphore (semaphore.Release()), and thus not allowing the execution to proceed.
But that is extremely weird (in my opinion), since the execution is on another thread than the semaphore blocking, and so it shouldn’t block, right?
I’ve tried to build a test case that reproduces the problem out of the context environment, but I can’t get it to reproduce. I post it here as an illustration of the 3 steps I explained earlier.
static class Earth
{
public const bool IsRound = true;
}
class Program
{
static Invoker Invoker = new Invoker();
static int i;
static void TestInvokingThread()
{
Invoker.Invoke(delegate { Thread.Sleep(300); }); // Simulate some work
}
static void TestExecutingThread()
{
while (Earth.IsRound)
{
Thread.Sleep(100); // Simulate some work
Invoker.Execute();
Thread.Sleep(100); // Simulate some work
}
}
static void Main(string[] args)
{
new Thread(TestExecutingThread).Start();
Random random = new Random();
Thread.Sleep(random.Next(3000)); // Enter at a random point
new Thread(TestInvokingThread).Start();
new Thread(TestInvokingThread).Start();
}
}
Output (as supposed to occur):
Executing 0 actions on thread 12
Executed, 0 actions left
Executing 0 actions on thread 12
Executed, 0 actions left
Invoking
Enqueued
Waiting on thread 13
Invoking
Enqueued
Waiting on thread 14
Executing 2 actions on thread 12
Actioned
Released
Waited
Actioned
Released
Waited
Executed, 0 actions left
Executing 0 actions on thread 12
Executed, 0 actions left
Executing 0 actions on thread 12
The actual question: What I’m asking, at this point, is if any experienced threading programmer can see a logical mistake in the Invoker class that could ever make it block, as I see no possible way of that happening. Similarly, if you can illustrate a test case that makes it block, I can probably find where mine went wrong. I don’t know how to isolate the problem.
Note: I’m quite sure this is not really regarded as a quality question for its specificity, but I’m mostly posting as a desperate cry for help, as this is hobby programming and I have no coworker to ask. After a day of trial and error, I still can’t fix it.
Important update: I just had this bug occur on the first invocation too, not necessarily only on the second. Therefore, it can really freeze by itself in the invoker. But how? Where?
I think you should lock on Actions when enqueuing and dequeuing. I occasionally had a null-reference exception here:
probably because a race condition.
I also think that the enqueued code should not dispose of the semphore, but just leave that to the enqueuing thread:
again because of race conditions.
EDIT: It has occurred to me that if the action throws an exception for some reason, the semaphore won’t be released, so:
Could that be the problem?
EDIT: Are you locking
this.Actionswhen modifying it?When dequeuing:
and enqueuing: