Aug 10 2011

Async CTP – Be Careful With Exceptions!

Category: Asynchrony,C#,Software DevelopmentWil @ 10:38 pm

I’m certainly no Bill Wagner, Jon Skeet or any of the other extremely intelligent and talented individuals who know their way around the new C# and VB asynchronous “stuff” coming soon from Microsoft. However, why can’t I throw my tattered and torn hat in the ring every now and then right?!

While preparing code for my talk at the upcoming Dallas TechFest 2011, and writing code samples to demonstrate how exceptions work with async and await, I ran across some nastiness that I really don’t think should be working, or in this case… breaking, the way it is in the refresh of the Async CTP. Since I could not find any mention of this issue on any website or forum (I’m not saying they don’t exist), I am going to show you it here.

Without any further ado, exceptions… inside an async method. Exceptions are supposed to propagate to the caller who is awaiting the completion of an async call. But… take this code for example:

public void Run()
{
	try
	{
		MyVoidAsyncMethod(null);

		Console.WriteLine("Press a key...");
		Console.ReadLine(); // <--- BIG problem!
	}
	catch (Exception ex_)
	{
		Console.WriteLine(ex_.Message);
	}
	Console.WriteLine("You'll never see this!");
}

private async void MyVoidAsyncMethod(string betterNotBeNull)
{
	if (string.IsNullOrEmpty(betterNotBeNull))
		throw new ArgumentNullException("That's why we can't have nice things!");

	await TaskEx.Yield();
}

Here’s what you get if you execute it:

Press a key…

Unhandled Exception: System.ArgumentNullException: Value cannot be null.
Parameter name: That’s why we can’t have nice things!

Server stack trace:
at DallasTechFest_2011.DeleteMe.d__0.MoveNext() in C:\DTF2011\DallasTechFest2011_Async\02 Exception Demos\DeleteMe.cs:line 27

Exception rethrown at [0]:
at System.Runtime.CompilerServices.AsyncVoidMethodBuilder.b__1(Object state)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
Press any key to continue . . .

The UI thread is blocked by the Console.ReadLine() and that totally hoses up the entire system… and it crashes the application immediately. This is an edge case and is definitely one of those, “Doctor, it hurts when I do this. Well, don’t do that!” situations. You probably shouldn’t be blocking the UI thread that called the async method anyway… so say’th the peanut gallery… and I agree.

However, it is inside a try/catch which “appears” to be completely ignored. This all has to do with the way the compiler generates the code for an async method. The first thing it is contractually obligated to do is to create an AsynVoidMethodBuilder object… and it does as you can see from the exception above. But since this is an ‘async voic’ method, it sort of freaks out… which I believe (ultimately) it shouldn’t.

Now, if you replace the async method with one that returns a Task<> like this:

private async Task<int> MyTaskAsyncMethod(string betterNotBeNull)
{
	if (string.IsNullOrEmpty(betterNotBeNull))
		throw new ArgumentNullException("That's why we can't have nice things!");

	await TaskEx.Yield();

	return 42;
}

Then… all you get for output is:

Press a key…

Moral? Not sure… maybe it’s don’t block your UI?! Maybe it’s just a hole that will be filled shortly by the C# compiler team before C# 5 ships. It’s worth noting at least. You’ve been warned!

Tags: ,


Oct 26 2009

CompiledQuery… I wanna have yo babies!

Category: C#,LINQ,Software Development,UncategorizedWil @ 9:21 pm

A co-worker and friend of mine, Dr. Steve Morrison, came to me the other day and asked if I had used the CompiledQuery (System.Data.Linq.CompiledQuery) class with LINQ-to-SQL. I told him that I had not and he said that he read that it was a big performance gain over just straight LINQ queries. Skeptical as I usually am, I let it sit on frontal lobe’s back burner for a day or so and occasionally asked Steve if he had done any testing with it yet. Today, he still hadn’t done any testing so I curiosity killed my patient little cat and I wrote a few tests to see what kind of performance improvements this “extra step” could make.

But first, if you plan on doing any of your own tests, you need to know how to clear SQL Server’s cache before each test run. You do that by executing the following SQL commands:

DBCC FREEPROCCACHE
GO
DBCC DROPCLEANBUFFERS
GO

I suggest putting that into a stored procedure and just calling it from LINQ-to-SQL (as I did in this test).

Now to the testing. My test database is a SQL Server 2008 database that has a decent amount of data in it; roughly 140K records in the one test table I use for this test. I am using LINQ-to-SQL (without the T4 templates) in C# for this test. All tests were run in release mode on a Quad-core PC running Windows 7 with 4GB of RAM.

And as a side note, no underscores were harmed in the making of these tests!  You have to be “in the know” to get that joke.

Test 1: Incremental record Count retrieval (from 1 record to 5000 records incrementally)
Basically, there is a loop from 1 to 5000. If the [Id] (the primary key of the table) is less than or equal to the index of the loop, I retrieve that record. As the loop progresses, the query will bring back more and more records. Eh, it’s a test… leave it at that.

Test 2: Get one record at a time… 5000 times
Again, there is a loop from 1 to 5000. If the [Id] (the primary key of the table) is equal to the index of the loop, I retrieve that record. The query will always return a single record… a different record… but a single one.

Here’s the non-compiled, normal LINQ-to-SQL:

// BaseTestClass has Logging and the Clear method... don't worry about it!
internal sealed class NonCompiledQueryTest : BaseTestClass
{
	public void Run()
	{
		ClearDatabaseCache();

		using (var dataContext = new TestDbDataContext(_connectionString))
		{
			dataContext.ObjectTrackingEnabled = false;

			Log(&amp;quot;Starting Non-Compiled Test&amp;quot;);
			DateTime start = DateTime.Now;
			for (int index = 0; index &amp;lt; 5000; index++)
			{
				var q = from r in dataContext.GetTable&amp;lt;MySampleTableFullOfData&amp;gt;()
				        where r.Id == index select r;
				q.ToList();
			}
			Log(&amp;quot;Non-compiled: &amp;quot; + DateTime.Now.Subtract(start).TotalSeconds);
		}
	}
}

And here’s the Compiled LINQ-to-SQL:

internal sealed class CompiledQueryTest : BaseTestClass
{
	public void Run()
	{
		ClearDatabaseCache();

		using (var dataContext = new TestDbDataContext(_connectionString))
		{
			dataContext.ObjectTrackingEnabled = false;

			Log(&amp;quot;Starting Compiled Test&amp;quot;);
			DateTime start = DateTime.Now;
			for (int index = 0; index &amp;lt; 5000; index++)
				Compiled(dataContext, index).ToList();

			Log(&amp;quot;Compiled: &amp;quot; + DateTime.Now.Subtract(start).TotalSeconds);
		}
	}

	static private readonly Func&amp;lt;TestDbDataContext, int, IQueryable&amp;lt;MySampleTableFullOfData&amp;gt;&amp;gt; Compiled =
		CompiledQuery.Compile&amp;lt;TestDbDataContext, int, IQueryable&amp;lt;MySampleTableFullOfData&amp;gt;&amp;gt;(
			(dc_, index_) =&amp;gt;
			from r in dc_.GetTable&amp;lt;MySampleTableFullOfData&amp;gt;()
			where r.Id == index_
			select r);
}

Results? Stop being so pushy! Here ya go:
CompiledQueryResults

Those numbers are the total number of seconds taken to run each test. I was completely blown away by the results. I honestly thought there was something wrong with the results from Test 2 so I ran that test as many different ways as I could… all produced the same exact results. That’s just awesome! Time to refactor some existing code!

The CompiledQuery improvements really shine when there are a lot of queries executing; not a single query that brings back a lot of records. In the later case, I found the compiled query to be about the same or just a smidgeon slower. With multiple queries… it just blows away normal, non-compiled, queries.

Enjoy!



W3Counter


Tags: , ,


Get Adobe Flash playerPlugin by wpburn.com wordpress themes