释放锁定不需要是监视器所有者的另一个线程

3

代码中有一个关键部分,由两个函数调用来限定,分别是 Start()End()。它们使用一个 Monitor 来阻塞其他线程的执行。现在我的问题是,如果某个线程由于任何原因都没有调用 End(),那么整个进程就会出现问题,因为每个线程都在等待这个 Monitor 被释放。

当然,我可以使用带有超时的 TryEnter,这样我就不会永远等待,但这并不会释放被阻塞的 Monitor,所以从此时开始,我的程序每次都会进入超时状态。

是否有一种方法,在给定超时时间后,从另一个线程中释放被阻塞的 Monitor

void Start(){ Monitor.Enter(obj); }

void End(){ Monitor.Exit(obj); }

编辑: 我们通过com互操作调用Excel,我们无法确保Excel进程总是如预期的那样工作。请注意这是一个网络应用程序,因此不能处理该情况是致命的。 Start()在第一次调用时被调用,请求调用Excel函数,End()在请求结束时被调用。Excel进程开始挂起的可能性始终存在。

编辑2: 我现在有了将ent锁所有者存储在变量中的想法,并且在死锁时,我可以杀死此线程。这会释放锁吗?

                        if (Monitor.TryEnter(excelLocker, 10000) == false)
                        {
                            excelOwner.Abort();
                            excelOwner = null;
                        }
                        else
                        {
                            excelOwner = Thread.CurrentThread;
                        }

为什么不在你的方法中设置一些东西,用while(...)循环直到达到某个阈值,然后在那一刻跳出循环呢? - Aaron McIver
2
不要试图在代码中修补错误,请修复错误。 - Hans Passant
@Hans Passant:这不是为了修复我们自己的错误。我们通过com互操作调用Excel,无法确定Excel进程始终按预期工作。请注意,这是一个Web应用程序,因此未能处理该情况是致命的。 - codymanix
@Aaron:你的意思是使用轮询而不是适当的锁定吗? - codymanix
基本上将其分解为典型的异步行为;所有异步操作都是相同的,因为您不知道它何时会返回。通过一种方法轮询,在应用程序中构建一个针对所有异步操作的超时机制,如果在给定的阈值内没有收到返回,则执行超时处理。 - Aaron McIver
3个回答

2
唯一可以释放锁定的线程是拥有该锁定的线程。因此,不,您不能直接从另一个线程“解除”监视器 - 这是设计上不可能的。如果您能够这样做,其他线程将能够通过在实际未拥有锁定的情况下释放锁定来覆盖锁定的语义。
我很想知道为什么您不使用lock块以保证EnterExit,而是直接使用Monitor
更新
阅读您的评论后,我强烈建议组织您的代码,以便您可以本地化锁定,而不是在请求开始和请求结束时。如果您使用锁定,仍然可以序列化访问Excel,但可以保证调用EnterExit
FYI lock在内部是一个Monitor
    lock(_syncObj)
    {
        //Do stuff
    }

    //Is equivalent to

    Monitor.Enter(_syncObj);
    try
    {
        //Do stuff
    }
    finally
    {
        Monitor.Exit(_syncObj);
    } 

使用lock,您可以按以下方式本地化锁定Excel:

    //Client code
    ExcelUtil.DoStuff("bling")

    //...

    //Util class manages call to Excel and locking.
    public static class ExcelUtil
    {
        private static readonly object SyncObj = new object();

        public static void DoStuff(string someParam)
        {
            //Guaranteed locking and unlocking even if an exception occurs
            lock (SyncObj)
            {
                DoSomeStuffWithExcelFuncA();
                DoSomeStuffWithExcelFuncB();
            }
        }

        private static void DoSomeStuffWithExcelFuncA()
        {
            //...
        }

        private static void DoSomeStuffWithExcelFuncB()
        {
            //...
        }
    }

作为旁注,为什么你要锁定对Excel的访问?我猜测你正在使用Excel自动化服务器端进行ASP.Net应用程序开发。除非这些技术已经有了重大进展,否则在几年前,这种方法总是存在很多问题的。如果你实施了锁定并且Excel卡住了,那么你就无能为力了。有一些第三方解决方案可以用于Excel自动化的代替。也许新版本的Excel更喜欢这样的使用方式呢?
你的模式似乎是序列化所有请求,以便只能同时执行一个(基于Excel的)请求——这似乎并不是很理想。

这是一个ASP应用程序。第一次调用Excel函数时会调用Start(),Request结束时会调用End()。还有其他锁定的方法吗?使用Mutex或AutoResetEvent可以实现我想要的吗?请参见我的编辑。 - codymanix

0
也许吧,但这只是掩盖了真正的问题。
你真正需要弄清楚的是为什么你的锁没有被释放。如果这是C++,你应该使用一个守卫(即使有异常抛出,锁也会被释放)。

0

>> 如果某个线程由于任何原因都没有调用End(),那么我的整个进程就会出问题,因为每个线程都在等待这个Monitor被释放。

让我们准确地说,我看到有两个原因导致某些线程不调用End():

  • 这个线程仍在执行中,这是可以接受的情况,除非您正在尝试使用此时不可用的资源并继续尝试。因此,如果您尝试手动停止此线程(从另一个线程中,如您所说),则您的数据将处于不一致状态的危险之中 - 就像调用Thread.Abort()一样。

  • 执行的正常流程被异常打断。因此,您需要在简单的try/finally块中清理资源并释放此监视器。

更新

如果Excel在高负载下不可用,它往往会抛出异常来通知您。最近在Code Review. StackExchange上讨论了这个主题以及如何处理这种情况。

另一种处理不确定等待锁的时间的情况的策略是使用Monitor.TryEnter(object, ref bool)。它专门设计用于在您不想在监视器上等待一段时间而是采取其他操作时的情况 - 因此您根本不会被阻塞。


第一个原因是我们在服务器上调用Excel,担心在重负载下它可能会开始阻塞。 - codymanix

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