我正在使用此问题中描述的调用:Synchronization accross threads / atomic checks?
我需要创建一个任何线程都可以调用的方法调用程序,它将在执行中的特定给定点的主执行线程上执行。
我最终使用了Invoker
类的这个实现:
我知道在锁定方面可能不是最有效的,但理论上它的工作方式与Thread.MemoryBarrier()
类似,例如SLaks建议的那样。
编辑:MRAB的建议。
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);
}
}
}
许多Console.WriteLine
可以帮助我跟踪我的冻结,无论是否存在这种记录,都会发生这种情况(即,他们不负责冻结,可以作为匪徒丢弃)。
冻结发生在以下情况:
Invoker.Execute
)。Invoker.Invoke
)。semaphore.Wait()
之后冻结。示例输出:
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
我怀疑发生的事情是执行线程以某种方式阻止,因此不会执行第二次排队的操作,也不会释放信号量(semaphore.Release()
),因此不允许执行继续进行。
但这是非常奇怪的(在我看来),因为执行是在另一个线程而不是信号量阻塞,所以它不应该阻塞,对吧?
我已经尝试构建一个测试用例,可以在上下文环境中重现问题,但我无法重现它。我在这里发布它作为我之前解释的3个步骤的例证。
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();
}
}
输出(应该发生):
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
实际问题:此时我要问的是,是否有任何有经验的线程程序员可以在Invoker
类中看到曾经< / em>让它阻止,因为我看不到发生这种情况的可能方式。同样,如果你能说明一个阻止它的测试用例,我可能会发现我的错误。 我不知道如何隔离问题。
注意:我很确定这并不是因为它的特殊性而被视为一个质量问题,但我主要是张贴作为绝望的呼救,因为这是业余爱好编程和我没有同事问。 经过一天的反复试验,我仍然无法修复它。
重要更新:我刚刚在第一次调用时遇到此错误,不一定只在第二次调用时发生。因此,它可以在调用者中自行冻结。但是怎么样?在哪里?
答案 0 :(得分:2)
我认为你应该在排队和出队时锁定动作。我偶尔会遇到一个空引用异常:
this.Actions.Dequeue()();
可能是因为竞争条件。
我也认为排队的代码不应该处理semphore,而只是把它留给排队的线程:
Console.WriteLine("Invoking");
SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);
this.Actions.Enqueue(delegate
{
action();
Console.WriteLine("Actioned");
semaphore.Release();
Console.WriteLine("Released");
});
Console.WriteLine("Enqueued");
Console.WriteLine("Waiting");
semaphore.Wait();
Console.WriteLine("Waited");
semaphore.Dispose();
再次因为竞争条件。
编辑:我发现如果动作由于某种原因抛出异常,则信号量不会被释放,所以:
this.Actions.Enqueue(delegate
{
try
{
action();
Console.WriteLine("Actioned");
}
catch
{
Console.WriteLine("Exception thrown by action");
throw;
}
finally
{
semaphore.Release();
Console.WriteLine("Released");
}
});
这可能是问题吗?
编辑:您在修改时是否锁定this.Actions
?
出列队列时:
Action action;
lock (this.Actions)
{
action = this.Actions.Dequeue();
}
action();
并加入队列:
lock (this.Actions)
{
this.Actions.Enqueue(delegate
{
...
});
}
答案 1 :(得分:0)
知道了。在我的程序中,这是一个非常深刻的多层死锁,因此我无法轻易地重现它。我会将MRAB的答案标记为已被接受,因为它可能是由Invoker本身引起的锁定的真正原因。