Asynchronous Regular Expressions using the ThreadPool and a cancellation model.
There are probably numerous ways to shoot holes in this class, but this is a 30 minute attempt at providing an asynchronous framework for running regular expressions. The crux of the model is to allow for cancellation of long-running expressions that might otherwise have a negative impact on your application or web server.
You need to start with an IAsyncResult implementation that supports the general asynchronous infrastructure. We'll also have a bunch of extra information for the regular expression work, namely an expression and input string. What I've come up with so far supports all of the features I need including cancellation, full reporting of passed in procedures on the async object for later processing of the work item, and fairly decent protection against the internal members controlling the asynchronous operation.
public class AsynchronousRegexResult : IAsyncResult {
internal object asyncState = null;
internal Regex expression = null;
internal Match match = null;
internal string inputString = null;
internal bool complete = false;
internal bool completedSynchronously = false;
internal bool cancelled = false;
internal ManualResetEvent waitHandle = null;
internal AsyncCallback callback = null;
internal Thread currentThread = null;
internal AsynchronousRegexResult(Regex innerExpression, string inputString, AsyncCallback callback, object state) {
this.asyncState = state;
this.expression = innerExpression;
this.inputString = inputString;
this.callback = callback;
if ( this.expression == null || this.inputString == null ) {
this.complete = true;
this.completedSynchronously = true;
}
this.waitHandle = new ManualResetEvent(this.completedSynchronously);
}
public object AsyncState { get { return this.asyncState; } }
public bool CompletedSynchronously { get { return this.completedSynchronously; } }
public bool IsCompleted { get { return this.complete; } }
public WaitHandle AsyncWaitHandle { get { return this.waitHandle; } }
public Regex Expression { get { return this.expression; } }
public string Input { get { return this.inputString; } }
public Match Result { get { return this.match; } }
public bool Cancel() {
if ( this.cancelled || this.complete ) {
return false;
}
this.cancelled = true;
if ( this.currentThread != null ) {
this.currentThread.Abort();
}
return true;
}
internal void Complete() {
this.complete = true;
this.waitHandle.Set();
if ( callback != null ) {
callback(this);
}
}
}
Three additional methods are needed to make this work. First, we need a BeginInvoke to launch our operation into the thread pool and an EndInvoke to retrieve the results. We don't really need the EndInvoke, but it builds in semantics for waiting on results if they aren't already available. Finally, the ThreadPool_WaitCallback will be the protected method that enables the true cancellation semantics and the ability to run our expressions.
using System;
using System.Threading;
using System.Text.RegularExpressions;
public sealed class AsynchronousRegex {
// Removed result class for brevity, but it is a public nested class.
public static IAsyncResult BeginInvoke(Regex innerExpression, string inputString, AsyncCallback callback, object state) {
#if DEBUG
Console.WriteLine("AsynchronousRegex.BeginInvoke::Enter");
#endif
AsynchronousRegexResult result = new AsynchronousRegexResult(innerExpression, inputString, callback, state);
if ( !result.complete ) {
#if DEBUG
Console.WriteLine("AsynchronousRegex.BeginInvoke::ThreadPool Queue");
#endif
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadPool_WaitCallback), result);
}
#if DEBUG
Console.WriteLine("AsynchronousRegex.BeginInvoke::Leave");
#endif
return result;
}
private static void ThreadPool_WaitCallback(object state) {
#if DEBUG
Console.WriteLine("AsynchronousRegex.ThreadPool_WaitCallback::Enter");
#endif
AsynchronousRegexResult result = state as AsynchronousRegexResult;
if ( result != null && !result.cancelled ) {
#if DEBUG
Console.WriteLine("AsynchronousRegex.ThreadPool_WaitCallback::Setting Current Thread");
#endif
result.currentThread = Thread.CurrentThread;
#if DEBUG
Console.WriteLine("AsynchronousRegex.ThreadPool_WaitCallback::Gathering Match");
#endif
try {
result.match = result.expression.Match(result.inputString);
} catch (ThreadAbortException) {
#if DEBUG
Console.WriteLine("AsynchronousRegex.ThreadPool_WaitCallback::Thread Aborted, Resetting");
#endif
Thread.ResetAbort();
} finally {
result.currentThread = null;
}
#if DEBUG
Console.WriteLine("AsynchronousRegex.ThreadPool_WaitCallback::Completing Asynchronous Result");
#endif
result.Complete();
}
#if DEBUG
Console.WriteLine("AsynchronousRegex.ThreadPool_WaitCallback::Leave");
#endif
}
public static Match EndInvoke(IAsyncResult ar) {
AsynchronousRegexResult result = ar as AsynchronousRegexResult;
if ( result != null ) {
result.AsyncWaitHandle.WaitOne();
return result.match;
} else {
throw new Exception("EndInvoke was called with the wrong async result");
}
}
}
As mentioned this class is not 100% thread-safe, but will work for the most part. I ran it for a couple of hours and didn't run into any troubles. Not a multi-threaded system though so concurrent access issues wouldn't surface easily (context switching might, but in most cases no).