Archive

I could not have received a clearer answer from Anders Hejlsberg after asking him, during PDC 2010, if there was any chance of getting AOP support in the next version of C#.

“No.”

I was relieved. But then Anders started spreading myths about AOP, not only showing that he’s misinformed about the technology, but also that he’s blatantly ignoring the needs of a large portion of C# developers – including some Microsoft teams.

Anders, I am totally relieved that C# will not support AOP. I understand your reasons, as the architect of Microsoft’s flagship language, but please allow me to respectfully demystify the untruths you’re spreading.

Myth 1. “Aspect-oriented programming is interesting for debugging and instrumentation of code and is not a full-fledged programming discipline.”

Truth 1. Anders probably stopped at the “Hello, world” example.

Although code instrumentation is certainly an important use case of AOP – and the one you would see in every “getting started” documentation – the technology significantly simplifies the work of developers when it comes to implement any non-trivial, real-life application. Just to cite a few real-life scenarios where AOP really helps:

  • Data Binding (INotifyPropertyChanged)
  • Data Validation
  • Thread switching (marshaling from/to background or GUI threads)
  • Thread synchronization (locking)
  • Security (authorization)
  • Transaction boundaries
  • Exception handling
  • Sanity checks (detection of memory leaks and deadlocks)
  • Transacted objects (objects supporting commit/rollback)
  • Persistent objects (whether to database, XML config file, registry, app.config, …)

Myth 2. ”You can take an arbitrary body of code that you did not write and inject your code in the middle of it and start messing with invariants that programmer thought were true and probably aren’t any more.”

Truth 2. Mature aspect-oriented frameworks offer a disciplined approach to programming

To Ander’s defense, let me acknowledge that AspectJ has been used for the ugly.

You can find some terrific examples in scientific literature where people have proudly been using aspects to fix bugs in a third party library, thereby turning AOP into a patching technology.

Let me be very clear about this: patching and AOP are different things, even if the same tools can be used. These cases are pretty much limited to Java, because .NET makes it much more complex to change code you have not written yourself (because of strong names).

PostSharp only enhances the code you own. It is a build-time tool. Even if PostSharp didn’t not look at the source code, the code is typically present on the developer’s machine. If you did not write that piece of code yourself, it was probably written by your colleague. As an architect, there are actually many aspects you want to be applied to the code written by line-of-business developers. You don’t want them to care about exception handling or change notification, so you define that these features should be added to all methods or properties of all domain objects. It takes you 20 lines of code in the base class and PostSharp writes the remaining 20,000 lines of code in business objects. This is aspect-oriented programming. This is actually what you want.

PostSharp won’t let you inject code in the middle of nowhere. PostSharp uses a model named composition filters, developed by the University of Twente. This model represents a method call as an exchange of messages (request/response) between the caller and the called nodes. Aspects work as filters on these messages. They can alter them, they can choose not to pass them forward, but they cannot change the internal behavior of nodes. If you apply several filters to the same node, PostSharp will require you to specify dependencies. The aspect dependency framework is engineered to handle situations when one uses several aspects from vendors who don’t know about each other. PostSharp is robust—by design.

PostSharp makes you aware of the presence of an aspect. If your code has been modified by an aspect – even if the aspect is located in another project – Visual Studio will underline it in light blue. Move the mouse pointer to the method name, and you’ll see the list of all aspects applied there. This is a unique feature of PostSharp 2.0 Pro.

Ok, I admit it. You can add an aspect to the method Math.Sin so that it always returns 2.67. Even if you can’t break the encapsulation of a method, you can add aspects to private methods of a class, arguably breaking the encapsulation of the class. Yes, you can do some powerful stuff with a sharp tool, and you can get hurt if you don’t use it correctly. Anders knows it. C# does native pointers, doesn’t it? The solution is not to give pros toy tools, but to make the tool safe by design.

Did you ever wonder why developers move away from static languages to dynamic languages? Partly, because the strong discipline of C# prevents them from doing what they need in an effective way. Whether Anders likes it or not, developers need meta-programming. If we as language designers don’t provide them with a disciplined approach to meta-programming, developers will move away from any disciplined language.

If governments stop listening to their voters, people start voting with their feet. This is exactly what’s happening now with C# folks moving to Ruby. And this is the gap PostSharp is trying to bridge: Anders, we love the way C# makes us write well-structured, machine-verifiable, refactorable code, but SharpCrafters listens when people ask for meta-programming, and we provide a framework that lets them make code that’s still machine-verifiable, still refactorable, and even better structured and maintainable than before. And this framework is PostSharp 2.0.

Myth 3. “A lot of the same things become possible with dependency injection.”

Truth 3. Using dependency injection for the sake of AOP is like buying a car for the purpose of sleeping on the back seats.

Dependency injection exists for a single purpose: to provide a specific implementation to objects requiring an interface. Dependency injection makes it practical to follow the design principle “program to an interface, not to its implementation”.

All dependency injection frameworks now come with some AOP-like feature: when you require the implementation of an interface, you don’t receive the implementation itself, instead you receive a proxy (called DynamicProxy), and this proxy applies some aspects before invoking the final implementation. It looks like AOP, it smells like AOP, but it hardly compares to a fully-fledged AOP framework.

The first issue is that dynamic proxies only allow you to add aspects to the explicit boundaries of a service, the ones you chose to expose as an interface. You can’t add aspects to private or static methods, even if you wanted to. You can’t add aspects to fields. Even if you added aspects to public methods, the aspects methods won’t be invoked when you call them from the same object (the call would not go through the proxy).

The second issue is more dramatic. As you become addicted to some of the benefits of AOP, you start using dynamic proxies even where you don’t need dependency injection, altering the architecture of your code just because of the requirements of dynamic proxy. And this is wrong. AOP does not require you to change the architecture of your code.

Dependency injection is a great way to isolate components and services. If you are isolating your domain objects with dynamic proxies, you’re probably doing something wrong.

And have you noticed you can’t use dynamic proxies on WinForms or WPF objects? Surely you want AOP there too.

Myth 4. “[Aspect weaving happens] later through a bytecode writer, that’s sort of where I get off the bus.”

Truth 4. Anders is not on the Microsoft Code Coverage or Visual Studio Code Coverage bus.

Many people have an esthetic resentment against MSIL rewriting. When asked for a rational explanation, people usually answer that it’s “sort of dirty”.

Why?

MSIL is well documented, the CLR behaves almost perfectly according to its specification, so what’s wrong in producing verifiable MSIL instructions? What’s wrong in modifying the assembly before it’s actually being tested?

Many tools use MSIL rewriting, including Microsoft Code Contracts and Visual Studio Code Coverage. I’m happy that PostSharp drives this bus.

Myth 5. “We have better ways of addressing these scenarios.”

Truth 5. Anders has better ways because he owns C#, but we don’t. We can’t wait for C# 12 to have a solution to NotifyPropertyChanged.

I agree. C# 5.0 will be wonderful. The new keywords async and await solve the problem of asynchrony in a wonderful way. There’s no irony here, I’ve written my thoughts on it previously: the solution is better by an order of magnitude than what we can do with AOP.

PostSharp users, however, have had some solution to this issue for four years. Not a perfect solution, but some solution. And most importantly: developers have been able to develop this solution themselves. They didn’t need to wait for the C# team, or for me – they had a toolkit that allowed them to write their own solution.

I fully understand that when the C# team picks up a problem it has – and succeeds – to find a magnificent ad-hoc solution. The point is: there are lots of issues where AOP can help, and if we had to wait for the language to come with an ad-hoc solution, we would probably wait forever.

What’s more, do we really need an ad-hoc solution every time? Wouldn’t it be better to abstract this set of issues and design a solution to the abstract problem? This solution is aspect-oriented programming.

Conclusion

Anders Hejlsberg fails to acknowledge the need for better support for meta-programming in C#, which is what AOP addresses. It’s not just about me and several thousand PostSharp users. Many teams in Microsoft itself have identified the same need. Windows Communication Framework (WCF) has its AOP framework (generalized into custom behaviors). ASP.NET MVC has its own (filters). Unity and PIAB have their own. Folks from Code Contracts would probably have appreciated the ability to hook the compiler. This need is largely being ignored.

There’s some speculation that C# 6.0 would “open the compiler”. From what I could hear from Anders Hejsberg and Mads Togersen, there’s no indication that the compiler would allow “extensions” to hook into the pipeline, much less modify its object model. Instead, we see the sign of fear of “someone messing with invariants” and of intractable cases to be handled by Microsoft Support. With regards to supportability, designing an extensible compiler is difficult, so I would not expect the first version “managed compiler” to support any extensibility. Therefore, within a horizon of 8-10 years (beyond which it’s difficult to predict anything anyway), I would not bet on an extensible compiler.

Therefore, PostSharp is there to stay. There’s a gap between the disciplined but inflexible programming model of C#, and the flexible but undisciplined model of dynamic languages. PostSharp bridges this gap, and PostSharp may well convince some folks to stay with C#.

Happy PostSharping!

-gael

In the first part of this article, I’ve shown how to use aspects to force method run on a specific type of thread – background or GUI. But putting operations on threads is only one side of the equation, and arguably the easiest one. What is much more difficult, is to avoid conflicts when accessing shared resources from different threads.

To get the source code of this article, install PostSharp, and go to directory:
C:\Program Files\PostSharp 2.0\Samples\.NET Framework 3.5\CSharp\Threading.

Because of this, we need to address the following issues:

  • How do I ensure that an object is in a consistent state when a thread reads it? How can I be sure that another thread is not modifying it at that particular moment?
  • How do I avoid two threads concurrently modifying the same object and breaking its consistency?
  • How do I prevent deadlocks?

In object-oriented programming, it often occurs that a significant part of the object model is a shared resource. This is typically the case with model objects in a Model-View-Controller. If the controller is allowed to modify the model from different threads, proper thread synchronization is necessary.

The Design Pattern

The first and most important thing to do when coping with thread synchronization is to identify good design patterns – there is no alternative to good design.

The design pattern I chose here is based on reader-writer locks (see the class ReaderWriterLockSlim). These locks allow concurrent reader threads, but the writer must have exclusive access. That is, a writer must wait for other readers, or other writers, to finish before starting, and will prevent them from starting until the writer finishes. Using these locks results in minimal thread contention (i.e. threads wait only minimally for each other) and deadlocks. Alas, they also result in an extensive amount of plumbing code.

Our design pattern possibly associates a lock with each object instance. However, if instance-level consistency is not enough, many instances can share the same lock. This is typically the case when one object is aggregated into another one. For instance, one may want an order and its order lines to always be consistent. In that case, all instances forming together in the same order would share the same lock.

As part of the design pattern, we decided that all synchronized objects should implement the IReaderWriterSynchronized interface:

public interface IReaderWriterSynchronized
{
    ReaderWriterLockSlim Lock { get; }
}

This interface will be useful when implementing the aspects.

Additionally, since implementing IReaderWriterSynchronized is still writing plumbing code, we would prefer a custom attribute to do it for us. Let’s call it ReaderWriterSynchronizedAttribute.

We further define custom attributes that, when applied to methods or property accessors, determine which kind of access to the object is required: ReaderAttribute, WriterAttribute or ObserverAttribute.

Any method that modifies the object, should be annotated with the [Writer] custom attribute. Methods that read more than one field of the object should also be annotated with the [Reader] custom attribute (it is useless to synchronize methods or property getters performing a single read access, because the operation is always consistent).

Let’s set aside the observer lock for the moment. The next piece of code illustrates a synchronized class: all its public members are guaranteed to perform consistently in a multithreaded environment.

[ReaderWriterSynchronized]
public class Person 
{
  public string FirstName { get; [Writer] set; }

  public string LastName { get; [Writer] set; }

  public string FullName
  {
      [Reader]
      get { return this.FirstName + " " + this.LastName; }
  }
}

Observer Locks

In an MVC design, the view is bound to model objects. Model objects expose events that are raised when they are updated (typically the PropertyChanged event of the INotifyPropertyChanged interface). The view (for instance, a data-bound WPF control) subscribes to this event.

In certain cases, it is crucial that the object does not get modified between the time the event is fired and the time it is processed by the view. One example is with observable collections in WPF (INotifyCollectionChanged interface). Since the NotifyCollectionChangedEventArgs object contains item indices, it is essential that these indices still refer to the same items when the event is processed.

So at first sight, it seems that we need to invoke events inside the writer lock, doesn’t it? Wrong; this would cause a deadlock. Indeed, remember that the view is bound to the GUI thread. Therefore, the PropertyChanged event handler is dispatched to the GUI thread:

[GuiThread]
private void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
  this.label.Text = ((Person)e).FullName;
}

When you bind a control to a domain object using WPF Data Binding, the thread dispatching is completed transparently.

When evaluating the FullName property, the GUI thread will require a read lock on the model object. However, the object is already locked by the writer! The GUI thread would therefore be required to wait for the worker thread to release the writer lock, but the worker thread has to wait until the GUI thread finishes the processing of the PropertyChanged event: we clearly would have a deadlock.

Therefore, we need a locking level that would prevent any other writer, but would allow concurrent readers.

This needed lock is called an upgradable reader lock: a reader lock that can be upgraded to a writer lock, then downgraded back to a reader lock. Upgradable readers allow concurrent readers, but forbid concurrent writers or upgradable readers. This is exactly what we need.

Instead of acquiring a writer lock, WriterAttribute will always acquire an upgradeable read lock and upgrade it to a write lock. As a result, it will be possible to downgrade it into an ObserverAttribute,

The following listing demonstrates how the Person class can be made observable, while ensuring multi-thread safety and avoiding deadlocks:

[ReaderWriterSynchronized]
public class Person : INotifyPropertyChanged
{
  private string firstName;
  private string lastName;

  public string FirstName
  {
      get { return this.firstName; }

      [Writer]
      set
      {
          this.firstName = value;
          this.OnPropertyChanged("FirstName");
      }

  }

  public string LastName
  {
      get { return this.lastName; } 

      [Writer]
      set
      {
          this.lastName = value;
          this.OnPropertyChanged("LastName");
      }
  }

  public string FullName
  {
      [Reader]
      get { return this.firstName + " " + this.lastName; }
  }


  [Observer]
  protected virtual void OnPropertyChanged(string propertyName)
  {
      if (this.PropertyChanged != null)
          this.PropertyChanged(this, 
      new PropertyChangedEventArgs(propertyName));
  }

  public event PropertyChangedEventHandler PropertyChanged;
}
You can also implement the interface INotifyPropertyChanged using PostSharp. This article is just about multithreading, so we won’t cover it.

Check List

We are now complete with our design pattern and need to distribute the following instructions to our team:

  • During analysis, identify which groups of objects should share locks. Most of the time, objects sharing the same lock form a tree.
  • Annotate synchronized objects with the [ReaderWriterSynchronized] custom attribute, or implement the IReaderWriterSynchronized interface manually.
  • Annotate with the [Reader] custom attribute all public read-only methods that perform more than one read operation.
  • Annotate with the [Writer] custom attribute all public methods modifying the object.
  • Implement events according to the standard design guidelines, but annotate the event-handling method (for instance OnPropertyChanged) with the [Observer] custom attribute.

Now, let’s look at the implementation of these custom attributes.

Implementing the ReaderWriterSynchronizedAttribute

This aspect must introduce a new interface into the target type and implement this interface. This can be realized easily by deriving the IntroduceInterface advice (an advice, in AOP jargon, is any code transformation).

[Serializable]
[IntroduceInterface(typeof(IReaderWriterSynchronized))]
public class ReaderWriterSynchronizedAttribute : InstanceLevelAspect, IReaderWriterSynchronized
{
  public override void RuntimeInitializeInstance(AspectArgs args)
  {
    base.RuntimeInitializeInstance( args );
    this.Lock = new ReaderWriterLockSlim();
  }

  [IntroduceMember]
  public ReaderWriterLockSlim Lock { get; private set; }
}

Because we derive our class from InstanceLevelAspect, our aspect will have the same lifetime as the instances of the classes to which it applies. In other words, we’ll have one instance of our aspect per instance of the target class. So fields of our aspects are actually “equivalent” to fields of the target class. The RuntimeInitializeInstance method is invoked from the constructor of the target classes; this is where we have to create the instance of the lock object. The IntroduceMember custom attribute instructs PostSharp to add the Lock property to the target class. The IntroduceInterface custom attribute does the same with the interface.

Implementing ReaderAttribute, WriterAttribute and ObserverAttribute

Before implementing an aspect, it’s good to ask oneself: how would we do it without aspects? What would the expanded code look like? To answer these questions, we would first have to determine if we already hold the lock and, if not, acquire it. We would have to enclose the whole method body in a try block and release the lock, if it was acquired, in the finally block. So our methods would look like this:

void MyMethod()
{
    bool acquire = !(this.myLock.IsWriteLockHeld || 
                     this.myLock.IsReadLockHeld ||
                     this.myLock.IsWriteLockHeld);

    if ( acquire )
        this.myLock.EnterReadLock();

    try
    {
        // Original method body.
    }
    finally
    {
        if ( acquire )
            this.myLock.ExitReadLock();
    }
}

PostSharp provides an appropriate kind of aspect for this transformation: OnMethodBoundaryAspect. It wraps the original method body inside a try…catch…finally block and gives us the opportunity to execute code before the method, upon successful execution, upon exception, and in the finally block. This is exactly what we need.

The following is the code for the Reader attribute. Note that the Writer and Observer attributes are similar.

[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method, 
                        TargetMemberAttributes = MulticastAttributes.Instance)]
public class ReadLockAttribute : OnMethodBoundaryAspect
{
  public override void OnEntry(MethodExecutionArgs args)
  {
    ReaderWriterLockSlim @lock = ((IReaderWriterSynchronized) args.Instance).Lock;

    if (!@lock.IsReadLockHeld && 
        !@lock.IsUpgradeableReadLockHeld &&
        !@lock.IsWriteLockHeld)
    {
        args.MethodExecutionTag = true;
        @lock.EnterReadLock();
    }
    else
    {
        args.MethodExecutionTag = false;
    }
  }

  public override void OnExit(MethodExecutionArgs args)
  {
    if ((bool) args.MethodExecutionTag)
    {
      ((IReaderWriterSynchronized) args.Instance).Lock.ExitReadLock();
    }
  }
}

In the preceding code, we implemented two handlers: OnEntry and OnExit. In order to obtain access to the ReaderWriterLockSlim object, we need to cast the target instance (available on the args.Instance property) to the IReaderWriterSynchronized interface and retrieve the Lock property.

The OnEntry method needs to store the information somewhere, whether the lock was acquired by us or not. Indeed, this information will be required by the OnExit method. For this purpose, we can use the args.MethodExecutionTag property. Whatever a handler stores in this property will be available to the other handlers.

Note the presence of the MulticastAttributeUsage custom attribute on the top of our class. It means that the aspect is to be used on instance methods only, so it is not to be used on constructors or on static methods.

Conclusions

Multithreaded programming can be simplified by adequately raising the level of abstraction. But why should every programmer care about synchronization primitives? In an ideal world, it should be enough if he or she annotates methods with a custom attribute determining the thread affinity or the locking level required by the method.

This is what we have demonstrated in this article by using six aspects: OnWorkerThread and OnGuiThread for thread affinity, and ReaderWriterSynchronized, Reader, Writer and Observer for the locking level.

However, multithreading is just one possible field of application of aspect-oriented programming. Caching, transaction management, exception handling, performance monitoring, and data validation are other concerns where aspect-oriented programming can advantageously be applied.

By providing a new way to encapsulate complexity, aspect-oriented programming results in shorter, simpler and more readable code, therefore being less expensive to write and maintain.

Following my previous post where I compare how AOP and C# 5.0 approach asynchronous programming, I am republishing, in two parts, an article where I explain how to aspect-oriented programming (and specifically PostSharp) make multithreaded programming easier.

Any non-trivial graphical application has to perform long operations such as reading and saving large files from disk, accessing the network, and carrying out expensive computations. However, implementing long operations without taking care of multithreading would utterly jeopardize user satisfaction.

Indeed, the graphical subsystem of Windows (on the top of which both .NET WinForms and WPF are built) is intrinsically single-threaded. It is based on a message queue where individual actions (processing a button click or rendering the dialog box) are executed one after the other. Therefore, when a button event handler executes, the progress bar cannot be rendered, even if its value has been updated. Neither can the user interface react properly to a click of the Cancel button.

Consequently, a golden rule for graphical programming is to never do anything long in the GUI thread. A few dozen milliseconds is the maximum we can afford to block the message queue, if we want users to be satisfied. And we do want this satisfaction.

The commonly used solution is to execute long operations in a worker thread. In the .NET Framework, it is generally considered best practice not to create a new thread for every operation, but instead, to queue a work item into the thread pool. This operation used to be difficult, but C# 2.0 and anonymous methods have fortunately made it easier.

The following piece of code handles clicks by using the Save button. It queues the I/O operation for asynchronous execution on a worker thread.

private void OnApplyClick(object sender, RoutedEventArgs e)
{
    ThreadPool.QueueUserWorkItem( () => this.contact.Save() );
}

Now, what if we want to display a message after the contact has been saved? Since the graphical subsystem is single-thread, we cannot invoke the MessageBox.Show method from the worker thread. Therefore, we have to dispatch it to the GUI thread. With WPF, we have to use a dispatcher object, as demonstrated in the following code sample:

private void OnApplyClick(object sender, RoutedEventArgs e)
{
    ThreadPool.QueueUserWorkItem( 
     delegate 
        { 
        this.contact.Save();
        this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
          new Action( () => MessageBox.Show(Window.GetWindow(this),"Contact Saved!") ));
       });
}

As can be seen, multithreading quickly makes the code unreadable and error-prone. Fortunately, there is a superior solution. What if we have the possibility to mark the affinity of methods directly to the worker thread, or GUI thread, and eliminate the plumbing code? It seems unrealistic, doesn’t it? But with PostSharp and aspect-oriented programming, it is not.

So let’s dream on. What we want are two custom attributes: the OnWorkerThreadAttribute, when the method should be executed asynchronously on a worker thread; and the OnGuiThreadAttribute, when the method should be executed on the GUI thread.

The above piece of code would look like this:

[OnWorkerThread]
private void OnApplyClick(object sender, RoutedEventArgs e)
{
   this.contact.Save();
   this.ShowMessage("Contact Saved!");
}

[OnGuiThread]
private void ShowMessage(string message)
{
    MessageBox.Show(Window.GetWindow(this), message);
}

Implementing the OnWorkerThread and OnGuiThread Attributes

If you are not already familiar with PostSharp, you may find it strange that custom attributes can actually change the behavior of methods. Indeed, these new custom attributes will have the effect of modifying the methods to which they are applied. But PostSharp is not an ordinary library: it is a tool that inserts itself in the build process and enhances the assemblies after the compiler did its job.

So the first thing to do is to download PostSharp and install it. Next, add PostSharp.dll to your project references.

We are now ready to develop our two aspects.

Both custom attributes will be derived from the class PostSharp.Aspects.MethodInterceptionAspect. They will intercept calls to the method to which they are applied.

Our first custom attribute, OnWorkerThreadAttribute, is trivial:

using System;
using System.Threading;
using PostSharp.Aspects;

namespace ContactManager.Aspects
{
    [Serializable]
    public class WorkerThreadAttribute : MethodInterceptionAspect
    {
        public override void OnInvoke(MethodInterceptionArgs args)
        {
            ThreadPool.QueueUserWorkItem( args.Proceed );
        }
    }
}

In this attribute, we implemented the method OnInvoke instead of the intercepted method.

The statement args.Proceed() then proceeds with the invocation of the intercepted method. As can be seen, the implementation of this aspect simply queues the execution of the intercepted method into the thread pool.

The implementation of the OnGuiThreadAttribute is a little more complex, because we first need to check if we are already on the GUI thread. If we are not, we need to invoke the intercepted method through Dispatcher.Invoke.

using System.Windows.Threading;
using PostSharp.Aspects;

namespace ContactManager.Aspects
{
  [Serializable]
  public class OnGuiThreadAttribute : MethodInterceptionAspect
  {
      public DispatcherPriority Priority { get; set; }

      public override void OnInvoke(
              MethodInterceptionArgs args)
    {
        DispatcherObject dispatcherObject = (DispatcherObject) args.Instance;

        if (dispatcherObject.CheckAccess())
        {
            // We are already in the GUI thread. Proceed.
            args.Proceed();
        }
        else
        {
            // Invoke the target method synchronously.  
            dispatcherObject.Dispatcher.Invoke(this.Priority, new Action(args.Proceed));
        }
    }
  }
}

The args parameter contains everything we need to know about the intercepted method. Here, we are interested in the target instance of the method, because we need to cast it and retrieve its dispatcher.

These two very simple custom attributes can have a significant impact on the way you think about multithreading. You can now forget about the thread pool and the WPF message dispatcher. All you have to think about is where the method should be executed: can it run asynchronously on a worked thread, or does it require a GUI thread? Using these two simple aspects makes your code easier to read and less error-prone.

Putting operations on threads is only one side of the equation, and arguably the easiest one. What is much more difficult, is to avoid conflicts when accessing resources that are shared by different threads. This is what I will address in the second part.