Click here to Skip to main content
Click here to Skip to main content
 
Add your own
alternative version
Go to top

Introducing the Model Thread View Thread Pattern

, 1 May 2010
Reduce threading code, and increase UI responsiveness with a new pattern extending MVVM.
Mtvt_2010_5_1__12_15.zip
Libraries
DanielVaughan
DanielVaughan.Silverlight.dll
DanielVaughan.Silverlight.pdb
Microsoft.Practices.ServiceLocation.dll
Microsoft.Practices.Unity.dll
System.ServiceModel.PollingDuplex.dll
System.Xml.Linq.dll
Prism
Silverlight
Microsoft.Practices.Composite.dll
Microsoft.Practices.Composite.pdb
Microsoft.Practices.Composite.Presentation.dll
Microsoft.Practices.Composite.Presentation.pdb
Microsoft.Practices.ServiceLocation.dll
System.Windows.Controls.dll
Mtvt
DanielVaughan.Mtvt.suo
DanielVaughan.Mtvt.vsmdi
Local.testsettings
TraceAndTestImpact.testsettings
DanielVaughan.MtvtExample
Bin
Debug
ar
bg
ca
cs
da
de
el
es
et
eu
fi
fr
he
hr
hu
id
it
ja
ko
lt
lv
ms
nl
no
pl
Properties
pt
pt-BR
ro
ru
sk
sl
sr-Cyrl-CS
sr-Latn-CS
sv
th
tr
uk
vi
zh-Hans
zh-Hant
Release
Collections
CommandModel
ComponentModel
Concurrency
Diagrams
OverviewDiagram.cd
Images
WpfDisciplesBanner.png
obj
Debug
DanielVaughan.MtvtExample.g.resources
DesignTimeResolveAssemblyReferences.cache
DesignTimeResolveAssemblyReferencesInput.cache
TempPE
Properties
Service References
UIModel
DanielVaughan.MtvtExample.Tests
Bin
Debug
ar
bg
ca
cs
da
de
el
es
et
eu
fi
fr
he
hr
hu
id
it
ja
ko
lt
lv
ms
nl
no
pl
Properties
pt
pt-BR
ro
ru
sk
sl
sr-Cyrl-CS
sr-Latn-CS
sv
th
tr
uk
vi
zh-Hans
zh-Hant
Release
Mocks
obj
Debug
DanielVaughan.MtvtExample.Tests.g.resources
DesignTimeResolveAssemblyReferencesInput.cache
ResolveAssemblyReference.cache
TempPE
Properties
DanielVaughan.MtvtExample.Web
bin
ClientBin
obj
Debug
DesignTimeResolveAssemblyReferencesInput.cache
TempPE
Properties
Prism
CAL
CAL.vsmdi
CompositeApplicationLibrary.4.5.resharper.user
CompositeApplicationLibrary_Desktop.4.5.resharper.user
LocalTestRun.testrunconfig
Desktop
Composite
OpenSource.snk
bin
Events
Logging
Modularity
obj
Debug
Composite.Desktop.csproj.GenerateResource.Cache
Microsoft.Practices.Composite.dll
Microsoft.Practices.Composite.pdb
Microsoft.Practices.Composite.Properties.Resources.resources
ResolveAssemblyReference.cache
TempPE
Release
Composite.Desktop.csproj.GenerateResource.Cache
Microsoft.Practices.Composite.dll
Microsoft.Practices.Composite.pdb
Microsoft.Practices.Composite.Properties.Resources.resources
TempPE
Properties.Resources.Designer.cs.dll
Properties
Regions
Composite.Presentation
OpenSource.snk
bin
Commands
Events
obj
Debug
Composite.Presentation.Desktop.csproj.GenerateResource.Cache
Microsoft.Practices.Composite.Presentation.dll
Microsoft.Practices.Composite.Presentation.pdb
Microsoft.Practices.Composite.Presentation.Properties.Resources.resources
ResolveAssemblyReference.cache
TempPE
Release
Composite.Presentation.Desktop.csproj.GenerateResource.Cache
Microsoft.Practices.Composite.Presentation.dll
Microsoft.Practices.Composite.Presentation.pdb
Microsoft.Practices.Composite.Presentation.Properties.Resources.resources
ResolveAssemblyReference.cache
TempPE
Properties.Resources.Designer.cs.dll
Properties
Regions
Behaviors
Silverlight
Composite
Composite.Silverlight.csproj.user
Migrated rules for Composite.Silverlight.ruleset
Bin
Events
Logging
Modularity
obj
Debug
Composite.Silverlight.csproj.GenerateResource.Cache
DesignTimeResolveAssemblyReferencesInput.cache
Microsoft.Practices.Composite.dll
Microsoft.Practices.Composite.pdb
Microsoft.Practices.Composite.Properties.Resources.resources
ResGen.read.1.tlog
ResGen.write.1.tlog
ResolveAssemblyReference.cache
TempPE
Properties.Resources.Designer.cs.dll
Release
Composite.Silverlight.csproj.GenerateResource.Cache
Microsoft.Practices.Composite.dll
Microsoft.Practices.Composite.pdb
Microsoft.Practices.Composite.Properties.Resources.resources
TempPE
Properties
Regions
Composite.Presentation
Composite.Presentation.Silverlight.csproj.user
Migrated rules for Composite.Presentation.Silverlight.ruleset
Bin
Commands
Events
obj
Debug
build.force
Composite.Presentation.Silverlight.csproj.GenerateResource.Cache
DesignTimeResolveAssemblyReferencesInput.cache
Microsoft.Practices.Composite.Presentation.dll
Microsoft.Practices.Composite.Presentation.pdb
Microsoft.Practices.Composite.Presentation.Properties.Resources.resources
ResGen.read.1.tlog
ResGen.write.1.tlog
ResolveAssemblyReference.cache
TempPE
Release
Composite.Presentation.Silverlight.csproj.GenerateResource.Cache
Microsoft.Practices.Composite.Presentation.dll
Microsoft.Practices.Composite.Presentation.pdb
Microsoft.Practices.Composite.Presentation.Properties.Resources.resources
ResolveAssemblyReference.cache
TempPE
Properties
Regions
Behaviors
LIB
Silverlight
ServiceLocation
Microsoft.Practices.ServiceLocation.dll
#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

Share

About the Author

Daniel Vaughan
President Outcoder
Switzerland Switzerland
Daniel Vaughan is a Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company dedicated to creating best-of-breed user experiences and leading-edge back-end solutions, using the Microsoft stack of technologies--in particular WPF, WinRT, Windows Phone, and also Xamarin.Forms.
 
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 Windows Phone apps including Surfy, Intellicam, and Splashbox; and is the creator of a number of popular open-source projects including Calcium SDK, and Clog.
 
Would you like Daniel to bring value to your organisation? Please contact

Daniel's Blog | MVP profile | Follow on Twitter
 
Windows Phone Experts
Follow on   Twitter   Google+   LinkedIn

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 1 May 2010
Article Copyright 2010 by Daniel Vaughan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid