Click here to Skip to main content
15,896,111 members
Articles / Desktop Programming / WPF

Introducing the Model Thread View Thread Pattern

Rate me:
Please Sign up or sign in to vote.
4.93/5 (69 votes)
1 May 2010BSD14 min read 168.2K   862   172  
Reduce threading code, and increase UI responsiveness with a new pattern extending MVVM.
#region File and License Information
/*
<File>
	<Copyright>Copyright © 2009, Daniel Vaughan. All rights reserved.</Copyright>
	<License>
	This file is part of DanielVaughan's base library

    DanielVaughan's base library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DanielVaughan's base library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with DanielVaughan's base library.  If not, see http://www.gnu.org/licenses/.
	</License>
	<Owner Name="Daniel Vaughan" Email="dbvaughan@gmail.com"/>
	<CreationDate>2010-04-22 17:26:58Z</CreationDate>
</File>
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using DanielVaughan.Collections;

namespace DanielVaughan.Concurrency
{
	/// <summary>
	/// Indicates the manner in which delegates are invoked.
	/// </summary>
	public enum DelegateInvocationMode
	{
		/// <summary>
		/// Delegates are sent to the thread.
		/// </summary>
		Blocking,
		/// <summary>
		/// Delegates are posted to the thread.
		/// </summary>
		NonBlocking
	}

	/// <summary>
	/// Managers a collection of <see cref="Delegate"/> instances.
	/// Allows Delegates to be referenced directly 
	/// or using a <see cref="WeakReference"/>. 
	/// Allows Delegates to be handled on the thread of subscription.
	/// </summary>
	public class DelegateManager
	{
		readonly bool preserveThreadAffinity;
		readonly DelegateInvocationMode invocationMode;
		readonly IProvider<ISynchronizationContext> contextProvider;
		readonly bool useWeakReferences = true;
		readonly List<DelegateReference> delegateReferences = new List<DelegateReference>();
		readonly object membersLock = new object();
		readonly Dictionary<DelegateReference, ISynchronizationContext> synchronizationContexts
			= new Dictionary<DelegateReference, ISynchronizationContext>();

		/// <summary>Initializes a new instance 
		/// of the <see cref="DelegateManager"/> class.</summary>
		/// <param name="preserveThreadAffinity">If set to <c>true</c>, 
		/// delegate invocation  will occur using the <see cref="ISynchronizationContext"/> 
		/// provided by the specified context provider.</param>
		/// <param name="useWeakReferences">If <c>true</c> weak references will be used.</param>
		/// <param name="invocationMode">The invocation mode. 
		/// If <c>Blocking</c> delegates will be invoked 
		/// in serial, other in parallel.</param>
		/// <param name="contextProvider">The context provider, 
		/// which is used to supply a context when a delegate is added.
		/// If preservedThreadAffinity is <c>false</c>, this value will be ignored.</param>
		public DelegateManager(bool preserveThreadAffinity = false,
			bool useWeakReferences = false, 
			DelegateInvocationMode invocationMode = DelegateInvocationMode.Blocking, 
			IProvider<ISynchronizationContext> contextProvider = null)
		{
			this.preserveThreadAffinity = preserveThreadAffinity;
			this.invocationMode = invocationMode;
			this.contextProvider = contextProvider;
			this.useWeakReferences = useWeakReferences;

			if (contextProvider == null)
			{
				this.contextProvider = new SynchronizationContextProvider();
			}
		}

		/// <summary>
		/// Adds the specified target delegate to the list of delegates 
		/// that are invoked when <see cref="InvokeDelegates"/> is called.
		/// </summary>
		/// <param name="targetDelegate">The target delegate.</param>
		public void Add(Delegate targetDelegate)
		{
			ArgumentValidator.AssertNotNull(targetDelegate, "targetDelegate");

			var reference = new DelegateReference(targetDelegate, useWeakReferences);

			lock (membersLock)
			{
				delegateReferences.Add(reference);
				if (preserveThreadAffinity)
				{
					synchronizationContexts[reference] = contextProvider.ProvidedItem;
				}
			}
		}

		/// <summary>
		/// Removes the specified target delegate from the list of delegates.
		/// </summary>
		/// <param name="targetDelegate">The target delegate.</param>
		public void Remove(Delegate targetDelegate)
		{
			lock (membersLock)
			{
				var removedItems = delegateReferences.RemoveAll(
					reference =>
					{
						Delegate target = reference.Delegate;
						return target == null || targetDelegate.Equals(target);
					});

				if (preserveThreadAffinity)
				{
					foreach (var delegateReference in removedItems)
					{
						synchronizationContexts.Remove(delegateReference);
					}
				}
			}
		}

		/// <summary>
		/// Invokes each delegate.
		/// </summary>
		/// <param name="args">The args included during delegate invocation.</param>
		/// <exception cref="Exception">
		/// Rethrown exception if a delegate invocation raises an exception.
		/// </exception>
		public void InvokeDelegates(params object[] args)
		{
			IEnumerable<DelegateReference> delegates;
			/* Retrieve the valid delegates by first trim 
			 * the collection of null delegates. */
			lock (membersLock)
			{
				var removedItems = delegateReferences.RemoveAll(
					listener => listener.Delegate == null);

				if (preserveThreadAffinity)
				{
					/* Clean the synchronizationContexts of those removed 
					 * in the preceding step. */
					foreach (var delegateReference in removedItems)
					{
						synchronizationContexts.Remove(delegateReference);
					}
				}
				/* The lock prevents changes to the collection, 
				 * therefore we can safely compile our list. */
				delegates = (from reference in delegateReferences
						   select reference).ToList();
			}

			/* At this point any changes to the delegateReferences collection 
			 * won't be noticed. */

			foreach (var reference in delegates)
			{
				if (!preserveThreadAffinity)
				{
					reference.Delegate.DynamicInvoke(args);
					continue;
				}

				var context = synchronizationContexts[reference];
				DelegateReference referenceInsideCloser = reference;
				Exception exception = null;

				var callback = new SendOrPostCallback(
					delegate
				        {
				            try
				            {
				                referenceInsideCloser.Delegate.DynamicInvoke(args);
				            }
				            catch (Exception ex)
				            {
				                exception = ex;
				            }
				        });

				switch (invocationMode)
				{
					case DelegateInvocationMode.Blocking:
						context.InvokeAndBlockUntilCompletion(callback, null);
						break;
					case DelegateInvocationMode.NonBlocking:
						context.InvokeWithoutBlocking(callback, null);
						break;
					default:
						throw new ArgumentOutOfRangeException("Unknown DispatchMode: " 
							+ invocationMode.ToString("G"));
				}

				/* Rethrowing the exception may be missed 
				 * in a DispatchMode.Post scenario. */
				if (exception != null) 
				{
					throw exception;
				}
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Engineer
Switzerland Switzerland
Daniel is a former senior engineer in Technology and Research at the Office of the CTO at Microsoft, working on next generation systems.

Previously Daniel was a nine-time Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company.

Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.

Daniel is the developer behind several acclaimed mobile apps including Surfy Browser for Android and Windows Phone. Daniel is the creator of a number of popular open-source projects, most notably Codon.

Would you like Daniel to bring value to your organisation? Please contact

Blog | Twitter


Xamarin Experts
Windows 10 Experts

Comments and Discussions