Я использую вызов, описанный в этом вопросе: Синхронизация между потоками / атомарные проверки?
Мне нужно создать метод вызова, который может вызывать любой поток, которыйбудет выполняться в главном исполняющем потоке в определенный момент его выполнения.
В итоге я использовал эту реализацию класса 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
). - В 2 других потоках вызывается 2 метода повторноодновременно (вызов
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
, которая могла бы когда-либо блокировать его, поскольку я не вижу возможного пути этого,Точно так же, если вы можете проиллюстрировать тестовый пример, который делает его блокированным, я, вероятно, смогу найти, где мой произошел сбой. Я не знаю, как изолировать проблему.
Примечание : Я совершенно уверен, что это не рассматривается как вопрос качества с точки зрения его специфики, ноЯ в основном пишу как отчаянный крик о помощи, так как это хобби, и у меня нет коллег, чтобы спросить. После дня проб и ошибок я все еще не могу это исправить.
Важное обновление: У меня только что эта ошибка возникала и при первом вызове, необязательно только на втором.Следовательно, он может действительно зависнуть сам по себе в invoker.Но как?Где?