using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
namespace Pfz.Threading.Cooperative
{
/// <summary>
/// Class that simulates a stack-saving mechanism (which does not exist in .Net).
/// If in the future such mechanism exists, this class may be killed or simple redirect to it.
/// At this moment, the "saved stack" runs in another thread completely synchronized with
/// the main one. But this can cause dead-locks if the caller holds a lock that the calle
/// should also use.
/// Aside from that, you can YieldReturn at any moment, even inside sub-methods that are
/// not enumerable.
/// </summary>
public sealed class UnsafeStackSaver:
IAdvancedDisposable,
IEnumerable<bool>,
IEnumerator<bool>
{
#if !SILVERLIGHT
internal static readonly object _syncAbortToken = new object();
#endif
#if DEBUG
private Thread _thisThread;
#endif
private readonly object _executePulser = new object();
private Action _action;
private bool _wasDisposed;
private bool _executing;
/// <summary>
/// Creates a new StackSaver that will run the given action, allowing it to
/// YieldReturn at any moment. It will not generate values, so it is used as
/// a synchronization mechanism.
/// </summary>
public UnsafeStackSaver(Action action)
{
_action = action;
UnlimitedThreadPool.Run(_Start);
}
/// <summary>
/// Releases the stack-saver. It will kill the executing method the same way a thread is aborted
/// (with a ThreadAbortException, calling only catches and finallys).
/// </summary>
public void Dispose()
{
lock(_executePulser)
{
_wasDisposed = true;
Monitor.Pulse(_executePulser);
while(_action != null)
Monitor.Wait(_executePulser);
}
}
/// <summary>
/// Gets a value indicating if a Dispose call was already made on this object,
/// even if it was not finalized yet.
/// </summary>
public bool WasDisposed
{
get
{
return _wasDisposed;
}
}
/// <summary>
/// Gets the exception that finished this stack-saver.
/// </summary>
public Exception Exception { get; private set; }
[ThreadStatic]
private static UnsafeStackSaver _current;
/// <summary>
/// When inside the action of a stack-saver, the stack-saver can be discovered by this.
/// If the return is null, then this method was called directly or by a value-producing stack-saver.
/// </summary>
public static UnsafeStackSaver Current
{
get
{
return _current;
}
}
[ThreadStatic]
internal static IValueProducerStackSaver _currentValueProducer;
/// <summary>
/// When inside the action of a stack-saver that produces values, the stack-saver can
/// be discovered by this. It will use a base interface, so you don't need to know its exact type
/// (it will work as long as you produce castable values).
/// If the return is null, then this method was called directly or by a non-value producing stack-saver.
/// </summary>
public static IValueProducerStackSaver CurrentValueProducer
{
get
{
return _currentValueProducer;
}
}
private void _Start()
{
try
{
var thisThread = Thread.CurrentThread;
thisThread.IsBackground = true;
#if DEBUG
_thisThread = thisThread;
#endif
_current = this;
lock(_executePulser)
_Wait();
_action();
}
#if !SILVERLIGHT
catch(ThreadAbortException abortException)
{
// if it is not our abort, rethrow.
if (abortException.ExceptionState != _syncAbortToken)
{
Exception = abortException;
throw;
}
// if it is our abort, it was used only to arrive here, so
// now we can resetabort and let the thread return to the pool.
Thread.ResetAbort();
}
#endif
catch(Exception exception)
{
Exception = exception;
}
finally
{
#if DEBUG
_thisThread = null;
#endif
_current = null;
_action = null;
_executing = false;
lock(_executePulser)
Monitor.PulseAll(_executePulser);
}
}
/// <summary>
/// Yield returns to a stack-saver that does not produce values, considering there is one.
/// If a stack-saver does not exist, this method throws an InvalidOperationException.
/// </summary>
public static void StaticYieldReturn()
{
var stackSaver = _current;
if (stackSaver == null)
throw new InvalidOperationException("This method is not managed by a non-returning stack-saver.");
stackSaver.YieldReturn();
}
/// <summary>
/// Yield returns to a typed stack-saver, considering there is one.
/// If a stack-saver does not exist, an InvalidOperationException is thrown.
/// Note that to return a string as an object, you should not allow
/// the type-inference.
/// </summary>
public static void StaticYieldReturn<T>(T value)
{
var instance = UnsafeStackSaver<T>._current;
if (instance != null)
{
instance.YieldReturn(value);
return;
}
var interfaced = _currentValueProducer;
if (interfaced == null)
throw new InvalidOperationException("This method is not managed by any returning stack-saver.");
interfaced.YieldReturn(value);
}
/// <summary>
/// YieldReturns to the caller of this stack-saver.
/// </summary>
public void YieldReturn()
{
#if DEBUG
if (Thread.CurrentThread != _thisThread)
throw new InvalidOperationException("Yield return may only be called by the action thread when it executes.");
#endif
lock(_executePulser)
{
_executing = false;
Monitor.Pulse(_executePulser);
_Wait();
}
}
private void _Wait()
{
while(true)
{
if (_wasDisposed)
{
#if SILVERLIGHT
Thread.CurrentThread.Abort();
#else
Thread.CurrentThread.Abort(_syncAbortToken);
#endif
// if the stacksaver was disposed, we must kill
// the actual thread. Calling abort on the actual thread
// does not cause an asynchronous exception, so it is safe.
}
if (_executing)
return;
Monitor.Wait(_executePulser);
}
}
/// <summary>
/// Executes the method of this stack-saver until it ends or until it calls
/// a YieldReturn.
/// Returns true if it YieldReturned, false if the action ended.
/// If an exception is thrown on the called method, it is rethrown as a
/// TargetInvocationException.
/// </summary>
public bool Execute()
{
BeginExecute();
return EndExecute();
}
/// <summary>
/// Executes the method of this stack-saver until it ends or until it calls
/// a YieldReturn.
/// Returns true if it YieldReturned, false if the action ended.
/// If an exception is thrown, it is stored in the Exception property.
/// </summary>
public bool ExecuteNoException()
{
BeginExecute();
return EndExecuteNoException();
}
/// <summary>
/// Starts the execution of the method of this StackSaver in parallel.
/// </summary>
public void BeginExecute()
{
lock(_executePulser)
{
if (_executing)
throw new InvalidOperationException("This object is already executing.");
if (_action == null)
throw new InvalidOperationException("The stacksaver already finished its execution.");
if (_wasDisposed)
throw new ObjectDisposedException(GetType().FullName);
_executing = true;
Monitor.Pulse(_executePulser);
}
}
/// <summary>
/// Waits until the action executed by the BeginExecute finishes.
/// If the action throws an exception, it is rethrown in this thread as a TargetInvocationException.
/// </summary>
/// <returns>true if the action yield returned, false if it ended.</returns>
public bool EndExecute()
{
bool result = EndExecuteNoException();
var exception = Exception;
if (exception != null)
throw new TargetInvocationException(exception);
return result;
}
/// <summary>
/// Waits until the action executed by the BeginExecute finishes.
/// If an exception is thrown on the other thread, it is rethrown here as a TargetInvocationException.
/// </summary>
/// <returns>true if the action yield returned, false if it ended.</returns>
public bool EndExecuteNoException()
{
lock(_executePulser)
{
while(_executing)
Monitor.Wait(_executePulser);
return _action != null;
}
}
#region IEnumerable<bool> Membres
IEnumerator<bool> IEnumerable<bool>.GetEnumerator()
{
return this;
}
#endregion
#region IEnumerable Membres
IEnumerator IEnumerable.GetEnumerator()
{
return this;
}
#endregion
#region IEnumerator<bool> Membres
bool IEnumerator<bool>.Current
{
get
{
return true;
}
}
#endregion
#region IEnumerator Membres
internal static readonly object BoxedTrue = true;
object IEnumerator.Current
{
get
{
return BoxedTrue;
}
}
bool IEnumerator.MoveNext()
{
return Execute();
}
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
#endregion
}
/// <summary>
/// Class that simulates a stack-saving mechanism (which does not exist in .Net).
/// If in the future such mechanism exists, this class may be killed or simple redirect to it.
/// At this moment, the "saved stack" runs in another thread completely synchronized with
/// the main one. But this can cause dead-locks if the caller holds a lock that the calle
/// should also use.
/// Aside from that, you can YieldReturn at any moment, even inside sub-methods that are
/// not enumerable.
/// </summary>
public sealed class UnsafeStackSaver<T>:
IAdvancedDisposable,
IEnumerable<T>,
IEnumerator<T>,
IValueProducerStackSaver
{
#if DEBUG
private Thread _thisThread;
#endif
private readonly object _executePulser = new object();
private Action _action;
private bool _executing;
private bool _wasDisposed;
/// <summary>
/// Creates a new StackSaver that will run the given action, allowing it to
/// YieldReturn at any moment. It will not generate values, so it is used as
/// a synchronization mechanism.
/// </summary>
public UnsafeStackSaver(Action action)
{
_action = action;
UnlimitedThreadPool.Run(_Start);
}
/// <summary>
/// Releases the stack-saver. It will kill the executing method the same way a thread is aborted
/// (with a ThreadAbortException, calling only catches and finallys).
/// </summary>
public void Dispose()
{
lock(_executePulser)
{
_wasDisposed = true;
Monitor.Pulse(_executePulser);
while(_action != null)
Monitor.Wait(_executePulser);
}
}
/// <summary>
/// Gets a value indicating if a Dispose call was already made on this object,
/// even if it was not finalized yet.
/// </summary>
public bool WasDisposed
{
get
{
return _wasDisposed;
}
}
/// <summary>
/// Gets the value generated by the last call to Execute.
/// </summary>
public T CurrentValue { get; private set; }
/// <summary>
/// Gets the exception that finished this stack-saver.
/// </summary>
public Exception Exception { get; private set; }
[ThreadStatic]
internal static UnsafeStackSaver<T> _current;
/// <summary>
/// When inside the action of a stack-saver, the stack-saver can be discovered by this.
/// If the return is null, then this method was called directly or by a value-producing stack-saver.
/// </summary>
public static UnsafeStackSaver<T> Current
{
get
{
return _current;
}
}
private void _Start()
{
try
{
var thisThread = Thread.CurrentThread;
thisThread.IsBackground = true;
#if DEBUG
_thisThread = thisThread;
#endif
_current = this;
UnsafeStackSaver._currentValueProducer = this;
lock(_executePulser)
_Wait();
_action();
}
#if !SILVERLIGHT
catch(ThreadAbortException abortException)
{
// if it is not our abort, rethrow.
if (abortException.ExceptionState != UnsafeStackSaver._syncAbortToken)
{
Exception = abortException;
throw;
}
// if it is our abort, it was used only to arrive here, so
// now we can resetabort and let the thread return to the pool.
Thread.ResetAbort();
}
#endif
catch(Exception exception)
{
Exception = exception;
}
finally
{
#if DEBUG
_thisThread = null;
#endif
_current = null;
UnsafeStackSaver._currentValueProducer = null;
_action = null;
_executing = false;
lock(_executePulser)
Monitor.PulseAll(_executePulser);
}
}
/// <summary>
/// YieldReturns to the caller of this stack-saver.
/// </summary>
public void YieldReturn(T value)
{
#if DEBUG
if (Thread.CurrentThread != _thisThread)
throw new InvalidOperationException("Yield return may only be called by the action thread when it executes.");
#endif
lock(_executePulser)
{
_executing = false;
CurrentValue = value;
Monitor.Pulse(_executePulser);
_Wait();
}
}
private void _Wait()
{
while(true)
{
if (_wasDisposed)
{
#if SILVERLIGHT
Thread.CurrentThread.Abort();
#else
Thread.CurrentThread.Abort(UnsafeStackSaver._syncAbortToken);
#endif
// if the stacksaver was disposed, we must kill
// the actual thread. Calling abort on the actual thread
// does not cause an asynchronous exception, so it is safe.
}
if (_executing)
return;
Monitor.Wait(_executePulser);
}
}
/// <summary>
/// Executes the method of this stack-saver until it ends or until it calls
/// a YieldReturn.
/// Returns true if it YieldReturned, false if the action ended.
/// If an exception is thrown on the called method, it is rethrown as a
/// TargetInvocationException.
/// </summary>
public bool Execute()
{
BeginExecute();
return EndExecute();
}
/// <summary>
/// Executes the method of this stack-saver until it ends or until it calls
/// a YieldReturn.
/// Returns true if it YieldReturned, false if the action ended.
/// If an exception is thrown, it is stored in the Exception property.
/// </summary>
public bool ExecuteNoException()
{
BeginExecute();
return EndExecuteNoException();
}
/// <summary>
/// Starts the execution of the method of this StackSaver in parallel.
/// </summary>
public void BeginExecute()
{
lock(_executePulser)
{
if (_executing)
throw new InvalidOperationException("This object is already executing.");
if (_action == null)
throw new InvalidOperationException("The stacksaver already finished its execution.");
if (_wasDisposed)
throw new ObjectDisposedException(GetType().FullName);
_executing = true;
Monitor.Pulse(_executePulser);
}
}
/// <summary>
/// Waits until the action executed by the BeginExecute finishes.
/// If the action throws an exception, it is rethrown in this thread as a TargetInvocationException.
/// </summary>
/// <returns>true if the action yield returned, false if it ended.</returns>
public bool EndExecute()
{
bool result = EndExecuteNoException();
var exception = Exception;
if (exception != null)
throw new TargetInvocationException(exception);
return result;
}
/// <summary>
/// Waits until the action executed by the BeginExecute finishes.
/// If an exception is thrown on the other thread, it is rethrown here as a TargetInvocationException.
/// </summary>
/// <returns>true if the action yield returned, false if it ended.</returns>
public bool EndExecuteNoException()
{
lock(_executePulser)
{
while(_executing)
Monitor.Wait(_executePulser);
return _action != null;
}
}
#region IEnumerable<T> Membres
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return this;
}
#endregion
#region IEnumerable Membres
IEnumerator IEnumerable.GetEnumerator()
{
return this;
}
#endregion
#region IEnumerator<T> Membres
T IEnumerator<T>.Current
{
get
{
return CurrentValue;
}
}
#endregion
#region IEnumerator Membres
internal static readonly object BoxedTrue = true;
object IEnumerator.Current
{
get
{
return BoxedTrue;
}
}
bool IEnumerator.MoveNext()
{
return Execute();
}
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
#endregion
#region IValueProducerStackSaver Membres
void IValueProducerStackSaver.YieldReturn(object value)
{
YieldReturn((T)value);
}
#endregion
}
}