Friday, January 16, 2009 8:57 AM
Easy deferred execution
While programming, especially in GUI applications, you may have come across the problem of actions that take too long to run them in the foreground thread (aka GUI thread).
.Net brings a couple of different possibilities to circumvent this, for example the ThreadPool, BackGroundWorker or the Dispatcher class in WPF. What they have in common is, that they clutter up your code.
Especially, if your are trying to
separate your concerns you should think of a better solution.
One possible solution could be to use a construct that separates the concern of handling the background calculation and just gives you the result when it's ready.
An interesting approach was presented before by
Ayende although in a different context (NHibernate) in his article
Future<TNHibernateQuery>.
The following code is an adoption of this to non-NHibernate context
You can also find the code for it here:
Future.cs and a test with example usage at:
FutureTest.cs.
The 'Guard' class used in the code will be the topic of a other blog post. In the mean time, you can find it here:
Gard.cs
///<summary>
///Class to move a calculation of into the background (<see cref="ThreadPool"/>).
///</summary>
///<typeparam name="TResult">Type of the result.</typeparam>
public class Future<TResult>
{
//---- Members -----------------------------------------------------
private TResult _value;
private Exception _exception;
private readonly IAsyncResult _asyncResult;
private readonly ManualResetEvent _lock;
private readonly Action<Future<TResult>> _callback;
//---------------------------------------------------------
///<summary>
///Create new instance and start the evaluation of the value.
///If an exception happens, it will be caught and exposed through the <see cref="Exception"/> property.
///</summary>
///<param name="action">Delegate to the evaluation function.</param>
public Future(Func<TResult> action) : this(action, future => { })
{
}
//---------------------------------------------------------
///<summary>
///Create new instance and start the evaluation of the value.
///If an exception happens, it will be caught and exposed through the <see cref="Exception"/> property.
///</summary>
///<param name="action">Delegate to the evaluation function.</param>
///<param name="callback">Delegate to be called after the evaluation.</param>
public Future(Func<TResult> action, Action<Future<TResult>> callback)
{
Guard.Against.Null.Argument(()=>callback);
_lock = new ManualResetEvent(false);
_callback = callback;
_asyncResult = action.BeginInvoke(
asyncResult =>
{
try
{
_value = action.EndInvoke(asyncResult);
}
catch (Exception ex)
{
_exception = ex;
}
finally
{
((ManualResetEvent)asyncResult.AsyncState).Set();
try
{
_callback(this);
}
catch (Exception ex)
{
Debug.Fail("Error while executing callback on future!", ex.ToString());
} //ignore the failed callback
}
},
_lock);
_callback = callback;
}
//---------------------------------------------------------
///<summary>
///Gets state of the calculation.
///</summary>
public bool IsCompleted
{
get { return _asyncResult.IsCompleted && _lock.WaitOne(0, false); }
}
//---------------------------------------------------------
///<summary>
///Calculated value. If the value is not yet calculated,
/// the calculation will be completed synchronously.
///</summary>
public TResult Value
{
get
{
if (!IsCompleted)
{
_asyncResult.AsyncWaitHandle.WaitOne();
_lock.WaitOne();
}
return _value;
}
}
//---------------------------------------------------------
/// <summary>
/// Exception that was thrown if any.
/// </summary>
public Exception Exception
{
get { return _exception; }
}
//---------------------------------------------------------
/// <summary>
/// Determines if there was an exception while executing the
/// evaluation.
/// </summary>
public bool HasThrownException
{
get { return _exception != null; }
}
}
Nothing Impossible
roboto