Нужна помощь в отслеживании резьбовой заморозки - PullRequest
1 голос
/ 18 июля 2011

Я использую вызов, описанный в этом вопросе: Синхронизация между потоками / атомарные проверки?

Мне нужно создать метод вызова, который может вызывать любой поток, которыйбудет выполняться в главном исполняющем потоке в определенный момент его выполнения.

В итоге я использовал эту реализацию класса 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 помогают мне отследить мое замораживание, что происходит независимо от того, присутствует ли эта регистрация (т. е. они не несут ответственности за замораживание и могут быть отброшены как преступники).

Замораживаниепроисходит в сценарии, в котором:

  1. Поток выполнения выполняется в цикле (вызов Invoker.Execute).
  2. В 2 других потоках вызывается 2 метода повторноодновременно (вызов Invoker.Invoke).
  3. Первый метод работает и вызывается нормально, но второй останавливается после «Ожидания», то есть после 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.Но как?Где?

Ответы [ 2 ]

2 голосов
/ 18 июля 2011

Я думаю, вы должны заблокировать действия при постановке в очередь и снятии очереди.Иногда у меня возникало исключение с нулевой ссылкой:

this.Actions.Dequeue()();

, вероятно, из-за условия гонки.

Я также думаю, что код в очереди не должен избавляться от семфора, а просто оставить это дляпоток постановки в очередь:

        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
                {
                    ...
                });
            }
0 голосов
/ 19 июля 2011

Понял.Это была действительно глубокая многослойная тупиковая ситуация во всей моей программе, поэтому я не мог воспроизвести ее легко.Однако я отмечу ответ MRAB принятым, поскольку он мог быть реальной причиной блокировок, вызванных самим Invoker.

...