Journal for November 2007

Be careful using anonymous delegates across threads

[UPDATE]: It has been pointed out to me that the premise of this article is wrong. The compiler specifically avoids this problem by hoisting the captured variables into an inner class of which the generated method is also an instance member, and creating a new instance of this class for each call. I'm not sure what bug I ran into that causes the behaviour I denounced, or whether it was simply sloppy handling of reference types on my part -- but I feel particularly stupid having vaguely remembered "inner classes" from reading Raymond Chen's fine explanation of this over a year ago, yet not having checked it before posting this article. Nonetheless, I shall let the article remain as a reminder of the code that this wonderful feature saves you from having to write. Also, the queue/lock-based code may still be useful when one needs data passed to the GTK thread in a guaranteed order.

Anonymous delegates are incredibly useful, especially in that they can "capture" variables from a parent scope. Used within a single thread, they are very easy to understand. However, if you're using them to pass data across threads, you need to understand how the variable capture works.

Consider the following code snippet, in which the "data" variable is captured from the delegate's parent scope.

void ProcessIncomingData (string data)
{
	Gtk.Application.Invoke (delegate (object sender, EventArgs e) {
			textBuffer.AddText (data);
		});
}

What's wrong with this?

Let's expand the anonymous method into something resembling the compiler-generated code.

void ProcessIncomingData (string data)
{
	compilerHoistedVariable = name;
	Gtk.Application.Invoke (new EventHandler (compilerGeneratedMethod));
}
 
string compilerHoistedVariable = null;
 
void compilerGeneratedMethod (object sender, EventArgs e)
{
	textBuffer.AddText (compilerHoistedVariable);
}

Notice that the variable has been "hoisted" into the class scope, so that the generated method used for the anonymous delegate can access it. (The actual compiler-generated code is a bit different, encapsulating the hoisted variables in an inner class, but the principle is the same).

Now consider what happens if ProcessIncomingData gets called by the data-processing thread several times before the GTK+ loop gets run and can handle the invocations. Only the most recent value remains in the compiler-hoisted variable, and the earlier data strings are lost.

This isn't a problem if you're passing transient state, for example the visibility state of a button. But if you're passing data, think carefully about the internal mechanics. You may have to implement locking on the shared data structure.

void ProcessIncomingData (string data)
{
	lock (textBufferQueue) {
		textBufferQueue.Enqueue (data);
		if (!queueHandlerRunning) {
			queueHandlerRunning = true;
			Gtk.Application.Invoke (
				delegate (object sender, EventArgs e) {
					lock (textBufferQueue) {
						while (textBufferQueue.Count > 0) {
							textBuffer.AddText (textBufferQueue.Dequeue ());
						}
						queueHandlerRunning = false;
					}
				});
		}
	}
}
bool queueHandlerRunning = false;
Queue<string> textBufferQueue = new Queue<string> ();

This could hardly be described as "pretty", and the anonymous delegate has grown so big that we may as well move its definition out into a conventional method.

Things are easier if the delegate consumer offers an alternate call allowing you to pass arguments along with the delegate, such as Gtk.Application.Invoke and its Invoke (object sender, System.EventArgs args, System.EventHandler d) overload.

void ProcessIncomingData (string data)
{
	Gtk.Application.Invoke (
		this,
		new dataEventArgs (data),
		delegate (object sender, dataEventArgs e) {
			textBuffer.AddText (dataEventArgs.Data);
		});
}
 
class dataEventArgs : EventArgs
{
	public string Data;
	public dataEventArgs (string data)
	{
		this.Data = data;
	}
}

This is much more verbose than the method with which we started, but at least you won't lose any data and don't have to worry about locking.

Sadly, the anonymous types in C# 3.0 can't be used to simplify this. For a start, they don't allow inheritance. One might think that the "object sender" argument could be abused to pass an anonymous type, however in order to leave the scope of a method, the anonymous type must be cast to "object". The only way to access its properties would be via reflection, which would not only be verbose but would also carry a performance penalty.

No votes yet

How not to break Mono installations

It's a bad idea to mess with the packaged version of Mono on your Linux distro by installing another version of Mono on top of it or into another of your distro's standard prefixes such as /usr/local. Your distro's developers, testers and packagers have tested the packaged version of Mono to make sure that it works with the various applications that depend on it, such as MonoDevelop, Tomboy, F-Spot, Beagle and Banshee. In addition, you're likely to end up with unusual errors due to mismatched bits and pieces interacting in unpredictable ways.

If you need a more recent version in order to test new features and bugfixes, please keep a properly separated parallel Mono environment. By following these instructions you can ensure that you don't affect your stable Mono platform while experimenting with newer versions.

This applies to platform bindings such as GTK#, GtkSourceView# and GNOME# too!

No votes yet

GtkSourceView 2 in MonoDevelop

I recently added support for GtkSourceView 2 to MonoDevelop, and it can be enabled with the "--enable-gtksourceview2" configure switch. Unfortunately the Boo Binding and the Database Addin depend on GtkSourceView# directly, and aren't compatible with the API changes in 2.0. I may get round to fixing them later. However, I've already switched on GtkSourceView2 support in my MonoDevelop build repository. The main reason I did this, of course, was the wonderful dark grey colour scheme "oblivion" :-)

The GtkSourceView2# binding isn't officially released yet, and will probably get absorbed into the Gnome# platform bindings, but right now it seems to work fine.
Packages for openSUSE 10.3 are available from my MonoDevelop build repository.

Notable new features are:

  • Improved syntax highlighting
  • Support for colour schemes

Notable missing/removed features are:

  • Printing
  • Bookmarks (also used for debugger breakpoints, when the debugger worked)

There isn't a UI for changing colour schemes yet, but it can be done manually in the ~/.config/MonoDevelop/MonoDevelopProperties.xml file by setting/adding the property <Property key="GtkSourceViewStyleScheme" value="oblivion" /> within the property <Property key="MonoDevelop.TextEditor.Document.Document.DefaultDocumentAggregatorProperties">.

No votes yet

MonoDevelop trunk builds

Hot on the heels of Lluis's MonoDevelop 1.0 Beta 2 announcement, I'd like to announce the availability of frequent builds of MonoDevelop from SVN trunk, packaged for openSUSE 10.3. If you'd like to get the latest and greatest bug fixes and features but don't want to build MD yourself, head over to my openSUSE Build Service Sandbox. The packages will also come up in the openSUSE search for MonoDevelop packages.

Warning: these packages are not guaranteed to be stable, though I'll try to push only usable revisions. I may also enable and disable features at a whim, e.g. disabling the Boo binding and MonoDevelop.Database addin in order to enable GtkSourceView 2 support.

Update: the sandbox link currently requires that you're logged into the openSUSE Build Service, so here's a direct link to the repository.

No votes yet