2013-11-28 3 views
3

Я пытаюсь реализовать базовый класс Future (да, я знаю о Task, но это для образовательных целей) и столкнулся с странным поведением Monitor класса. Класс реализуется так, чтобы он входил в конструктор lock, и он запускает действие, которое завершает блокировку пула потоков. Результат getter проверяет переменную экземпляра, чтобы увидеть, завершено ли действие, а если нет, вводится блокировка и затем возвращается результат. Проблема в том, что на самом деле результат getter не ждет завершения действия в очереди и продолжения, что приводит к неправильным результатам. Вот код.Монитор, похоже, не блокирует объект

// The class itself 
public class Future<T> 
{ 
    private readonly Func<T> _f; 
    private volatile bool _complete = false; 
    private T _result; 
    private Exception _error = new Exception("WTF"); 
    private volatile bool _success = false; 
    private readonly ConcurrentStack<Action<T>> _callbacks = new ConcurrentStack<Action<T>>(); 
    private readonly ConcurrentStack<Action<Exception>> _errbacks = new ConcurrentStack<Action<Exception>>(); 
    private readonly object _lock = new object(); 

    public Future(Func<T> f) 
    { 
     _f = f; 
     Monitor.Enter(_lock); 
     ThreadPool.QueueUserWorkItem(Run); 
    } 

    public void OnSuccess(Action<T> a) 
    { 
     _callbacks.Push(a); 
     if (_complete && _success) 
      a(_result); 
    } 

    public void OnError(Action<Exception> a) 
    { 
     _errbacks.Push(a); 
     if (_complete && !_success) 
      a(_error); 
    } 

    private void Run(object state) 
    { 
     try { 
      _result = _f(); 
      _success = true; 
      _complete = true; 
      foreach (var cb in _callbacks) { 
       cb(_result); 
      } 
     } catch (Exception e) { 
      _error = e; 
      _complete = true; 
      foreach (var cb in _errbacks) { 
       cb(e); 
      } 
     } finally { 
      Monitor.Exit(_lock); 
     } 
    } 

    public T Result { 
     get { 
      if (!_complete) { 
       Monitor.Enter(_lock); 
      } 
      if (_success) { 
       return _result; 
      } else { 
       Console.WriteLine("Throwing error complete={0} success={1}", _complete, _success); 
       throw _error; 
      } 
     } 
    } 

     // Failing test 
     public void TestResultSuccess() { 
     var f = new Future<int>(() => 1); 
     var x = f.Result; 
     Assert.AreEqual (1, x); 
    } 

Я использую Mono 3.2.3 на Mac OS X 10.9.

+0

разные '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' на вашей стороне. Вам, похоже, нужен общий '_lock'. – Vlad

+0

Есть ли у Mono 'Task '/'TaskCompletionSource '? 'cos, что было бы намного проще ... –

+0

@ Vlad предположительно OP говорит о «в контексте одного будущего» ... –

ответ

6

Только резьба, которую заняла, замок может выйти из замка. Вы не можете Enter в конструкторе на вызывающем потоке, затем Exit из пула потоков, когда он завершается, - рабочий поток пула не имеет блокировки.

И наоборот: предположительно это тот же поток, который создал будущее, который обращается сорбент: что разрешено к Enter снова: это Реентрантная. Кроме того, вам нужно Exit столько же раз, сколько вы Enter, иначе оно фактически не будет выпущено.

В принципе, я не думаю, что Monitor - правильный подход здесь.

+0

Спасибо, я пропустил эту часть в документации 'Monitor'. Каков правильный примитив синхронизации для использования здесь? – synapse

+0

@synapse, если вы просто хотите, чтобы он работал (т. Е. Фокусировался на простом), затем 'ManualResetEvent'. Если вы хотите, чтобы он работал ** эффективно **, то это, вероятно, было бы чем-то, связанным с проверками флажков с блокировкой, и т. Д. –

+0

спасибо, работал как шарм с 'ManualResetEvent' – synapse

Смежные вопросы