CodeToast


Categories


Archives

Meta

Towards cleaner code, a C# Asynchronous Helper (Pt 2 of 3)

Posted by Nicholas Brookins on 24 September, 2008

// The Code.

When we last were together, I spent entirely too long explaining the wrong and/or hard ways to start a quick asynchronous task in C#, and only gave a quick taste of what was to come with the Async helper class. This time I’ve got the source to back me up.

UPDATE: The first part of this series is now an article on CodeProject. Check it out and give it a vote for me.

 

// The main method: Async.Do(…);

Our Do() method is where most of the magic happens, although it is only 60 lines with generous comments. Sharp observers will notice a couple odd things: it is private, and it takes two different delegate parameters. There are several overloads, these in turn call this method passing one or the other delegate, depending on whether we need a return value. This cleans the caller’s code greatly.

 

Making one method that does the work keeps the important logic (and troubleshooting) in one place, but it is private as to not confuse the caller as to which delegate to use.

 


private static AsyncRes Do(DlgR dr, Dlg d, bool getRetVal, object state,
bool tryThreadPool, ReenteranceMode rMode) {
	//get a generic MethodInfo for checks..
	MethodInfo mi = ((dr != null) ? dr.Method : d.Method);
	//make a unique key for output usage
	string key = string.Format("{0}{1}{2}{3}", ((getRetVal) ? "<-" : ""),
		mi.DeclaringType, ((mi.IsStatic) ? ":" : "."), mi.Name);
	//our custom return value, holds our delegate, state, key, etc.
	AsyncRes res = new AsyncRes(state, ((dr != null) ?
		(Delegate)dr : (Delegate)d), key, rMode);

	//Create a delegate wrapper for what we will actually invoke..
	Dlg dlg = (Dlg)delegate {
		//checks for reentrance issues and sets us up
		if (!BeforeInvoke(res)) return;
		try {
			if (res.IsCompleted) return;
			if (dr != null) {
				res.retVal = dr();//use this one with a return
			} else {
				d();//otherwise the simpler dlg
			}<br />
		//we never want a rogue exception, it can't bubble up anywhere
		} catch (Exception ex) {
			Console.WriteLine("Async Exception:" + ex);
		} finally {
			//this will fire their callback and clean up
			FinishInvoke(res);
		}
	};

	//...//

		


So far we have obtained a MethodInfo object that gives more detail about the delegate we will be executing, created an instance of the AsyncResult custom class that will be returned to the caller for tracking, and then wrapped their delegate into another anonymous method/delegate that can be bandied about as needed. This wrapper also injects method calls for before and after the invoke, these help with locking for our reentrance as well as setting some properties on the result object.

 

Don’t worry, the overhead of this extra indirection of this wrapper method is remarkable small (I benchmarked it) unless you are talking thousands of tasks executing per second. If that is the case then you need to rethink your architecture. Now for the rest of our Do() method:

 

	 

	if (tryThreadPool) { //we are going to use the .NET threadpool
		try {
			//get some stats - much better than silently failing 
			//or throwing an expensive exception
			int minThr, minIO, threads, ioThr, totalThr, totalIO;
			ThreadPool.GetMinThreads(out minThr, out minIO);
			ThreadPool.GetAvailableThreads(out threads, out ioThr);
			ThreadPool.GetMaxThreads(out totalThreads, out totalIO);

			//check for at least our thread + one more in ThreadPool
			if (threads > minThreads) {
				//this is what actually fires this task off..
				bool result = ThreadPool.QueueUserWorkItem(
					(WaitCallback)delegate { dlg(); });
				if (result) {
					res.result = AsyncAction.ThreadPool;
					//this means success in queueing the item
					return res;
				} else {
					//according to docs, this "won't ever happen" 
					// but an exception instead.. who knows.
					Console.WriteLine( "Failed to queue in threadpool." +
						"Method: " + key);
				}
			} else {
				Console.WriteLine(String.Format(
					"Insufficient idle threadpool threads: {0}of{1}-min {2},
					Method: {3}", threads, totalThreads, minThreads, key));
			}
		} catch (Exception ex) {
			Console.WriteLine("Failed to queue in threadpool: " + ex.Message +
				"- Method: " + key);
		}
	}

	//if we got this far, then something failed, or they wanted a dedicated thread
	Thread t = new Thread((ThreadStart)delegate { dlg(); });
	t.IsBackground = true; //this & priority are candidates for additional settings
	t.Name = "Async_" + key;
	res.result = AsyncAction.Thread;
	t.Start();

	return res;
}
		


In the latter part of our method we check some threadpool stats to see if it is worth even trying to use it. If all is good, we queue up the delegate and we’re done. If it is out of threads or the user chose a dedicated thread, then we skip that hullabaloo and just make our own thread to run.

 

// Before and After methods

Injected into our wrapper delegate we have a BeforeInvoke and FinishInvoke. There are a few important concepts there so let’s check them out quickly.

 


private static bool BeforeInvoke(AsyncRes res) {
	//if marked as completed then we abort.
	if (res.IsCompleted) return false;
	//if mode is 'allow' there is nothing to check.  Otherwise...
	if (res.RMode != ReenteranceMode.Allow) {
		//be threadsafe with our one and only member field
		lock (methodLocks) {
			if (!methodLocks.ContainsKey(res.Method)) {
				// make sure we have a generic locking object in the coll. 
				// it will already be there if we are reentering
				methodLocks.Add(res.Method, new object());
			}
			//if bypass mode and we can't get or lock, we dump out.
			if (res.RMode == ReenteranceMode.Bypass) {
				if (!Monitor.TryEnter(methodLocks[res.Method])) {
					res.result = AsyncAction.Reenterant;
					return false;
				}
			} else {
				//Otherwise in 'stack' mode, 
				//we just wait until someone else releases it...
				Monitor.Enter(methodLocks[res.Method]);
			}
			//if we are here, all is good.  
			//Set some properties on the result class to show when 
			//and what thread we are on
			res.isStarted = true;
			res.startTime = DateTime.Now;
			res.thread = Thread.CurrentThread;
		}
	}

	return true;
}


Here we look for ’stack’ or ‘bypass’ modes which require locking, allow mode can just play on though. We first look for a locking object in our methodLocks collection, keyed by our delegate’s fully qualified method name. The lock object is unimportant, just something that can be synchronized against - I chose not to use any existing objects because the caller might have access to them - which could cause problems if they also decided to lock on them.

 

Once we have our lock we Monitor.TryEnter (and fail otherwise) in ‘bypass’ mode, or just Monitor.Enter the lock and wait forever for it to free in ’stack’ mode.

 

That collection is the only thing we keep track of at a class level - everything else cleans up itself. Now for our FinishInvoke:

 


private static void FinishInvoke(AsyncRes res) {
	if (res == null) return;
	try {
		//finish a few more properties
		res.isCompleted = true;
		res.completeTime = DateTime.Now;
		// set the resetevent, in case someone is using the handle to 
		// know when we've completed.
		res.mre.Set();
	} catch (Exception ex) {
		Console.WriteLine("Error setting wait handle on " +
			(res.Method ?? "NULL") + ex);
	}

	if (res.RMode != ReenteranceMode.Allow) {
		//if mode is bypass or stack, then a lock that needs releasing
		try {
			if (methodLocks.ContainsKey(res.Method)) {
				Monitor.Exit(methodLocks[res.Method]);
			}
		} catch (Exception ex) {
			Console.WriteLine("Error releasing lock on " +
				(res.Method ?? "NULL")+ ex);
		}
	}
}


In FinishInvoke we set a few more properties, including a ManualResetEvent that can be used by the caller to wait on this task’s completion. Then we release our lock to allow other instances of this task to proceed.

 

The overloads and AsyncResult class are about all that is left in the code, and they aren’t especially groundbreaking, so I’ll leave it to you to check them out. This zip file has a VS2005 project that I used to make sure this compiled by itself, and this Async.cs file has the entire source code, a little over 300 lines with lots of comments.

 

UPDATE: Part three of the series expands on this class, before downloading the code you may want to read the next part and get the source there with added UI functionality.

 

Later on I may post some interesting uses for this, as well as some expanded functionality that I’ve already been using. I hope this can be as useful to you as it has for me - use it in good health, and don’t hesitate to post questions, comments, or suggestions.

 

*Coming in future posts of this series: Async tasks in a Windows Forms GUI environment (Part 3), expanding this async concept into timers for recurring tasks, a handy stop watch, and configuration file handling.







4 Responses to “Towards cleaner code, a C# Asynchronous Helper (Pt 2 of 3)”


  1. adam Says:

    Has the future post been completed? This is an excellent tutorial and helped me understand the async wrapper theory. I’d love to follow up with the items you have listed for the next part!!

  2. adam Says:

    sorry… just saw the first link to part 3 (the second link you have posted does not go anywhere). It will be great to see the recurring tasks, config file and stop watch functionality.

  3. Rosaria Kinne Says:

    I’m so happy that there is justice for the poor murdered young ladies

  4. Dorris Guckin Says:

    Jeez. That Khloe Kardashian is crazy. I love following them just because they’ve always got something goofy going on. Hope you keep posting stuff about the Kardashians.


Leave a Reply