Here is a thread safe logger without the monitor2 or blocking queue. It manually does the same thing in code without the other classes:
using System;
using System.Collections;
using System.IO;
using System.Threading;
using Microsoft.SPOT;
namespace MyApp
{
public class Logger : IDisposable
{
StreamWriter sw;
Queue q;
AutoResetEvent wait = new AutoResetEvent(false);
bool isDisposed;
public Logger(string path)
{
if (path != null) // Allow testing without a file.
sw = new StreamWriter(path, true);
q = new Queue();
new Thread(ReaderLoop).Start();
}
public void Write(string msg)
{
if (msg == null) throw new ArgumentNullException("msg");
lock (q)
{
ThrowIfDisposed();
q.Enqueue(msg);
wait.Set();
}
Debug.Print("Logger wrote: " + msg);
}
public void Close()
{
lock (q)
{
if (isDisposed) return;
this.isDisposed = true;
q.Enqueue(null);
wait.Set(); // signal reader if waiting.
if (sw != null)
sw.Close();
}
}
public void Dispose()
{
this.Close();
}
private void ThrowIfDisposed()
{
if (this.isDisposed)
throw new InvalidOperationException("Disposed");
}
private void ReaderLoop()
{
while (!isDisposed)
{
string s = Read();
if (s == null) break;
if (this.sw!=null)
sw.Write(s);
else
Debug.Print("Logger wrote: " + s);
}
Debug.Print("Logger Reader thread exited.");
}
private string Read()
{
Monitor.Enter(q);
try
{
if (isDisposed) return null;
while (q.Count == 0)
{
Wait(); // Wait for producer.
}
string msg = q.Dequeue() as string;
return msg;
}
finally
{
Monitor.Exit(q);
}
}
private bool Wait(int millisecondsTimeout=-1, bool exitContext = true)
{
// Exit lock before wait. Need to own it to exit it.
Monitor.Exit(q);
try
{
// If we timeout waiting for Pulse, return false.
return wait.WaitOne(millisecondsTimeout, exitContext);
}
finally
{
// Always re-enter the lock after wait.
Monitor.Enter(q);
}
}
}
}