互斥锁/监视器不能用于c#线程同步

本文关键字:用于 线程 同步 不能 监视器 | 更新日期: 2023-09-27 18:03:28

我试图只允许一个Threadobject访问非静态方法。我并不真正创建线程,而是使用任务。我试图将该方法锁定到一个用法,但它只是不起作用,我首先尝试了一个静态Mutex对象,然后是一个非静态Mutex对象,但即使有一个简单的监视器,它也不起作用。

我代码:

private async void LoadLinkPreview()
    {
        try
        {
            // TODO: FEHLERTRÄCHTIG!
            Monitor.Enter(_loadLinkMonitor);
            Debug.WriteLine(DateTime.Now + ": Entered LinkPreview");
            // Code ...
            // There are also "await" usages here, don't know if this matters
        }
        catch { }
        finally
        {
             Monitor.Exit(_loadLinkMonitor);
             Debug.WriteLine(DateTime.Now + ": Left LinkPreview");
        }
    }

,这是控制台显示的内容:

3/12/2013 6:10:03 PM: Entered LinkPreview
3/12/2013 6:10:05 PM: Entered LinkPreview
3/12/2013 6:10:05 PM: Left LinkPreview
3/12/2013 6:10:06 PM: Left LinkPreview

当然,有时它会按照想要的顺序工作,但我希望它总是工作。有人能帮帮我吗?

编辑:我意识到控制台显示可能是想要的行为,因为"左"WriteLine()是在监视器退出后,但我现在切换了它们,可以确认肯定有两个线程同时工作!还有一个更明显的控制台结果:

3/12/2013 6:26:15 PM: Entered LinkPreview
3/12/2013 6:26:15 PM: Entered LinkPreview
2 had an error! // Every time the method is used I count up now, this confirms that the second
// time the method is called the error is produced, which would not happen if the monitor
// is working because I have an if statement right upfront ...
3/12/2013 6:26:17 PM: Left LinkPreview
3/12/2013 6:26:18 PM: Left LinkPreview

互斥锁/监视器不能用于c#线程同步

实际上有可能你的代码按预期工作,而你的问题只是与显示有关。

问题在这里:

 Monitor.Exit(_loadLinkMonitor);
 Debug.WriteLine(DateTime.Now + ": Left LinkPreview");

在退出监视器后写出时间戳。请记住,等待进入监视器的其他代码可以在调用Exit后立即开始执行。因此,当前线程可以调用exit,然后在Debug调用之前,另一个线程可以Enter监视器并将其写入"enter"行。

如果您将代码更改为:

 Debug.WriteLine(DateTime.Now + ": Left LinkPreview");
 Monitor.Exit(_loadLinkMonitor);

那么它将不会改变行为,但您将删除日志错误。如果仍然乱序,那么您的代码中确实有问题。

如果你有一个async方法,你应该避免调用阻塞等待的方法,比如Monitor.Enter。如果你使用SemaphoreSlim,你可以使用它的WaitAsync方法,以确保该方法不会阻塞。它看起来像这样:

private SemaphoreSlim semaphore = new SemaphoreSlim(1);
private async void LoadLinkPreview()
{
    try
    {
        await semaphore.WaitAsync();
        Debug.WriteLine(DateTime.Now + ": Entered LinkPreview");
        await Task.Delay(2000);//placeholder for real work/IO
    }
    finally
    {
        Debug.WriteLine(DateTime.Now + ": Left LinkPreview");
        semaphore.Release();
    }
}

编辑:抛弃所有这些。答案是:为什么我不能使用'await'在锁语句体中的操作符?

你不应该做你想做的事情,使用"await"本质上是一个"lock"语句。

不能在async方法中使用Monitor等标准多线程原语。如果你使用lock语句,c#编译器会阻止你,但如果你通过Monitor.Enter"手动"执行,那么你最终会搬起石头砸自己的脚。

如果你稍微想一下,这是完全有道理的。当async方法产生时会发生什么?它返回一个未完成的Task,并且允许其他代码在等待时运行。这完全违背了锁的目的,锁的目的是在锁被持有时防止其他代码运行。也有一些情况(例如ASP。. NET或线程池上下文),其中async方法在另一个线程上恢复;在这种情况下,一个线程获取一个锁,另一个线程释放它。精神错乱!猫和狗住在一起!

内置的。net解决方案是SemaphoreSlim,它作为锁/信号量工作(尽管它没有内置IDisposable用于using)。Stephen Toub写了一个关于async协调原语的博客系列,我在我的AsyncEx库中实现了一个完整的套件,可以通过NuGet获得。