Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /////////////////////////////////////////////////////////////////////////////////
- // paint.net //
- // Copyright (C) dotPDN LLC, Rick Brewster, and contributors. //
- // All Rights Reserved. //
- /////////////////////////////////////////////////////////////////////////////////
- // RELEASED INTO THE PUBLIC DOMAIN -- Do what you want with it, but I'm not liable for anything. -Rick Brewster
- using System;
- using System.Threading;
- namespace PaintDotNet.Threading
- {
- // This code goes along with this Twitter thread: https://twitter.com/rickbrewPDN/status/1215811335805521920
- // version 2 -- January 11th, 2020
- // FIXED: Previous version had a bug whereby the first call to Exit() on the first
- // thread that called Enter() would end up signaling the event. We need the
- // _last_ call to Exit() on the _first_ thread that called Enter() to do
- // the signaling. Otherwise re-entrancy support is completely broken.
- // Also worth pointing out is that this class is trusting that you use it correctly.
- // A malicious thread could just call Exit() without having ever called Enter() and
- // it would break you.
- /// <summary>
- /// Implements a critical section that can be unlocked precisely once. This is useful
- /// for guarding lazy initialization and avoiding a lock convoy once the value is
- /// published.
- ///
- /// The first thread that calls Enter will acquire the lock, and then all other threads
- /// will block until that first thread calls Exit. At that point, all threads will be
- /// released and all further calls into Enter will return immediately. This helps to
- /// avoid a lock convoy once the lock is released by the first thread.
- /// </summary>
- public sealed class SingleUseCriticalSection
- {
- private const int invalidThreadID = -0x0b4db33f; // thread IDs can't be negative
- private int enterCount;
- private int exitCount;
- private volatile ManualResetEvent resetEvent;
- private int ownerThreadID = invalidThreadID;
- public SingleUseCriticalSection()
- {
- this.enterCount = 0;
- this.resetEvent = new ManualResetEvent(false);
- }
- public void Enter()
- {
- if (Interlocked.Increment(ref this.enterCount) == 1)
- {
- this.ownerThreadID = Thread.CurrentThread.ManagedThreadId;
- }
- else if (Thread.CurrentThread.ManagedThreadId == this.ownerThreadID)
- {
- // Support re-entrant access on the same thread.
- // We won't necessarily get a correct value for this if we're not on the thread
- // that entered the lock. It's possible that 2 threads race to call Enter(),
- // one will get the lock and set the thread ID, and the other will call Enter()
- // and get invalidThreadID for the thread ID. However, since that isn't a valid
- // value for a managed thread ID, this will be okay.
- // In order to make sure that the _last_ call to Exit() on the _first_ thread
- // that called Enter() is the one that signals the event, we perturb the exit count
- Interlocked.Decrement(ref this.exitCount);
- }
- else
- {
- ManualResetEvent resetEvent = this.resetEvent;
- if (resetEvent != null)
- {
- resetEvent.WaitOne();
- }
- }
- }
- public void Exit()
- {
- if (Interlocked.Increment(ref this.exitCount) == 1)
- {
- ManualResetEvent resetEvent = this.resetEvent;
- this.resetEvent = null;
- resetEvent.Set();
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement