监视器似乎无法锁定对象。

3

我正在尝试实现一个基本的Future类(是的,我知道有Task,但这是为了教育目的),并遇到了Monitor类的奇怪行为。该类的实现方式是在构造函数中进入锁定状态,将退出锁定的操作排队到线程池。结果获取器检查实例变量以查看操作是否已完成,如果未完成,则进入锁定状态,然后返回结果。问题在于,实际上结果获取器不等待排队的操作完成,而是继续进行,导致结果不正确。以下是代码。

// 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);
    }

我正在使用Mac OS X 10.9上的Mono 3.2.3。


Mono有Task<T>/ TaskCompletionSource<T>吗?因为那会简单得多... - Marc Gravell
@Vlad 假定 OP 是在谈论“在单个 future 的上下文中”... - Marc Gravell
@MarcGravell:不太确定:毕竟,每个Future只在其构造函数中锁定一次。 - Vlad
@Vlad,请看一下 Result 访问器:它进入了。 - Marc Gravell
@Marc:你说得对。我刚刚看了代码,那里没有其他访问权限。 - Vlad
显示剩余3条评论
1个回答

6
只有取得锁的线程才能释放该锁。你不能在构造函数上调用线程中Enter,然后在线程池完成后从线程池中Exit - 线程池工作者没有该锁
反过来说:假设访问getter的是创建future的同一个线程:这是允许的,可以再次Enter:这是可重入的。此外,你需要ExitEnter相同的次数,否则它实际上并没有被释放。
基本上,我认为Monitor不是这里正确的方法。

谢谢,我在“Monitor”文档中漏掉了那部分。这里应该使用什么适当的同步原语? - synapse
如果你只是想让它工作(即专注于简单),那么使用ManualResetEvent。如果你想让它高效地工作,那么可能涉及到交错的标志检查等内容。 - Marc Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接