Any multithreading CF libraries?

  • Thread starter Thread starter Jon Skeet [C# MVP]
  • Start date Start date
J

Jon Skeet [C# MVP]

The CF doesn't have nearly as much scope as the full framework with the
Monitor class. In particular, it's missing
Wait/Pulse/PulseAll/TryEnter. I believe most of these (those which
don't require timeouts, at least) can be emulated with a separate class
which internally uses normal locks and Manual or Auto reset events.
Does anyone know of a well-written/tried/tested open source class which
does this?

If not, and if I write one (which I'll probably need to for my current
project), is anyone willing to have a look at it and help me fix up any
problems?
 
I've not seen one, but I'd love to add one to the OpenNETCF libraries and
would gladly help develop and test it.
 
Chris Tacke said:
I've not seen one, but I'd love to add one to the OpenNETCF libraries and
would gladly help develop and test it.

Great. When it comes to the bit of project when it's needed, I'll be
back in touch...
 
Chris Tacke said:
I've not seen one, but I'd love to add one to the OpenNETCF libraries and
would gladly help develop and test it.

Righto, well, here's a start which I've run a few tests on, and seems
to work. Apologies for the formatting - I don't keep my code in VS.NET
to 72 columns, which is what I use to post.


Sample usage is something like:

FullMonitor monitor = new FullMonitor();

monitor.Enter();
try
{
// Do some stuff, possibly including monitor.Pulse etc
}
finally
{
monitor.Exit();
}




using System;
using System.Threading;

namespace Whatever
{
/// <summary>
/// Fuller Monitor implementation than CF.NET supplies.
/// </summary>
public sealed class FullMonitor
{
/// <summary>
/// The owner of the monitor, or null if it's not owned
/// by any thread.
/// </summary>
Thread currentOwner=null;

/// <summary>
/// Number of levels of locking (0 for an unowned
/// monitor, 1 after a single call to Enter, etc).
/// </summary>
int lockCount=0;

/// <summary>
/// Object to be used as a monitor for state changing.
/// </summary>
object stateLock = new object();

/// <summary>
/// AutoResetEvent used to implement Wait/Pulse/PulseAll.
/// Initially not signalled, so that a call to Wait will
/// block until the first pulse.
/// </summary>
AutoResetEvent waitPulseEvent = new AutoResetEvent(false);

/// <summary>
/// Number of threads waiting on this monitor.
/// </summary>
int waitCounter=0;

/// <summary>
/// Event used for Enter/Exit. Initially signalled
/// to allow the first thread to come in.
/// </summary>
AutoResetEvent enterExitEvent = new AutoResetEvent(true);

/// <summary>
/// Creates a new monitor, not owned by any thread.
/// </summary>
public FullMonitor()
{
}

/// <summary>
/// Enters the monitor (locks it), blocking until the
/// lock is held. If the monitor is already held by the current thread,
/// its lock count is incremented.
/// </summary>
public void Enter()
{
Thread currentThread = Thread.CurrentThread;
while (true)
{
enterExitEvent.WaitOne();
lock (stateLock)
{
if (currentOwner==null)
{
currentOwner=currentThread;
lockCount=1;
enterExitEvent.Reset();
return;
}
else if (currentOwner==currentThread)
{
lockCount++;
return;
}
}
}
}

/// <summary>
/// Attempts to enter the monitor (locking it) but does not block
/// if the monitor is already owned.
/// </summary>
/// <returns>Whether or not the current thread now owns the monitor.
/// </returns>
public bool TryEnter()
{
lock (stateLock)
{
if (currentOwner==null)
{
currentOwner=Thread.CurrentThread;
lockCount=1;
enterExitEvent.Reset();
return true;
}
else if (currentOwner==Thread.CurrentThread)
{
lockCount++;
return true;
}
return false;
}
}

/// <summary>
/// Releases a level of locking, unlocking the monitor itself
/// if the lock count becomes 0.
/// </summary>
/// <exception cref="SynchronizationLockException"
/// <exception cref="SynchronizationLockException">If the current
/// thread does not own the monitor.</exception>
public void Exit()
{
lock (stateLock)
{
if (currentOwner != Thread.CurrentThread)
{
throw new
SynchronizationLockException
("Cannot Exit a monitor owned by a different thread.");
}
lockCount--;
if (lockCount==0)
{
currentOwner=null;
enterExitEvent.Set();
}
}
}

/// <summary>
/// Pulses the monitor once - a single waiting thread will be released
/// and continue its execution after the current thread has exited the
/// monitor. Unlike Pulse on the normal framework, no guarantee is
/// made about which thread is woken.
/// </summary>
/// <exception cref="SynchronizationLockException">If the
/// current thread does not own the monitor.</exception>
public void Pulse()
{
lock (stateLock)
{
if (currentOwner != Thread.CurrentThread)
{
throw new
SynchronizationLockException
("Cannot Exit a monitor owned by a different thread.");
}
// Don't bother setting the event if no-one's waiting - we'd only end
// up having to reset the event manually.
if (waitCounter==0)
{
return;
}
waitPulseEvent.Set();
waitCounter--;
}
}

/// <summary>
/// Pulses the monitor such that all waiting threads are woken up.
/// All threads will then try to regain the lock on this monitor.
/// No order for regaining the lock is specified.
/// </summary>
/// <exception cref="SynchronizationLockException">If the current
/// thread does not own the monitor.</exception>
public void PulseAll()
{
lock (stateLock)
{
if (currentOwner != Thread.CurrentThread)
{
throw new
SynchronizationLockException
("Cannot Exit a monitor owned by a different thread.");
}
for (int i=0; i < waitCounter; i++)
{
waitPulseEvent.Set();
}
waitCounter=0;
}
}

/// <summary>
/// Relinquishes the lock on this monitor (whatever the lock count is)
/// and waits for the monitor to be pulsed. After the monitor has been
/// pulsed, the thread blocks again until it has regained the lock (at
/// which point it will have the same lock count as it had before), and
/// then the method returns.
/// </summary>
public void Wait()
{
int oldLockCount;
lock (stateLock)
{
if (currentOwner != Thread.CurrentThread)
{
throw new
SynchronizationLockException
("Cannot Exit a monitor owned by a different thread.");
}
oldLockCount = lockCount;
// Make Exit() set the enterExitEvent
lockCount=1;
Exit();
waitCounter++;
}
waitPulseEvent.WaitOne();
Enter();
// By now we own the lock again
lock (stateLock)
{
lockCount=oldLockCount;
}
}
}
}


using System;

namespace Whatever
{
/// <summary>
/// Exception thrown by FullMonitor when threading rules
/// are violated (usually due to an operation being
/// invoked on a monitor not owned by the current thread).
/// </summary>
public class SynchronizationLockException : Exception
{
public SynchronizationLockException(string message) :
base(message)
{
}
}
}
 
Jon,

Thanks! I'll add it (with slight name changes) to the OpenNETCF SDF code,
which should be released in the next couple days. That'll get it in the
hands of many testers quickly.

--
Chris Tacke, eMVP
Co-Founder and Advisory Board Member
www.OpenNETCF.org
---
Windows CE Product Manager
Applied Data Systems
www.applieddata.net
 
Chris Tacke said:
Thanks! I'll add it (with slight name changes) to the OpenNETCF SDF code,
which should be released in the next couple days. That'll get it in the
hands of many testers quickly.

Righto - just be warned that all it's had is some unit tests applied to
it, and writing unit tests for MT code is far from simple. A few good
pairs of eyes looking at it closely before too many people actually
start relying on it would be a good idea :)
 
Back
Top