Multithreading and unit tests woes

169 views
Skip to first unread message

Laurent Bugnion, GalaSoft

unread,
Nov 20, 2009, 6:51:14 PM11/20/09
to wpf-di...@googlegroups.com

Hey guys,

 

I am trying to unit test some dispatcher extensions that check for cross thread access, and if it is the case, BeginInvoke on the UI thread. The code is written, tested in Silverlight, but I am having some issues with the unit test runner in WPF. To unit test multithreaded code, I use a ManualResetEvent that I set in the spawned thread. Since I am testing setting properties on a UI element, I then DoEvents (code borrowed from our Disciplette) and finally I assert that the UI element’s property has been set.

 

Obviously when I test without dispatching, I get an exception, no issues there. With the dispatching, no exception. You could say the test is successful if I don’t have an exception.

 

However, I would like to test that the property has, indeed, been set from the spawned thread. This check succeeds when I run the unit test alone, or if I debug the test, but it fails if I run all the tests (note: I am using MS Tests, and tried both the built in runner and the Resharper runner, with the same result).

 

To be honest, I am not sure I can solve that, and it’s OK if this cannot be solved, but just curious if one of you gurus did that kind of things already, and have a trick I missed.

 

Code (I removed the SL code for clarity)

 

private const string NewContent = "New content";

private Button _button;

 

[TestMethod]

public void TestDispatchingToUiThread()

{

    _button = new Button

    {

        Content = "Content1"

    };

 

    var manualEvent = new ManualResetEvent(false);

 

    var thread = new Thread(()=>

    {

        Thread.Sleep(500);

        DispatcherExtensions.CheckBeginInvokeOnUi(() => AccessMethodOnUiThread(NewContent));

        manualEvent.Set();

    });

 

    DispatcherExtensions.Initialize();

 

    thread.Start();

 

    manualEvent.WaitOne(30000);

 

    DoEvents();

    Assert.AreEqual(NewContent, _button.Content);

}

 

private void AccessMethodOnUiThread(string newContent)

{

    _button.Content = newContent;

}

 

public void DoEvents()

{

    var frame = new DispatcherFrame();

    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,

        new DispatcherOperationCallback(ExitFrames), frame);

    Dispatcher.PushFrame(frame);

}

 

public object ExitFrames(object f)

{

    ((DispatcherFrame)f).Continue = false;

    return null;

}

 

 

--

Laurent Bugnion [Microsoft MVP, MCP]

Blog: http://blog.galasoft.ch

Web: http://www.galasoft.ch

Support children in Calcutta: http://www.calcutta-espoir.ch

 

cid:image001.png@01C9C8AA.B722DA80

My

business

card

as

Microsoft Tag

 

 

image001.png

Eric Burke

unread,
Nov 20, 2009, 9:05:07 PM11/20/09
to wpf-di...@googlegroups.com
When you say the test fails when running all tests, is it throwing/asserting, or is it some other failure?

Laurent Bugnion

unread,
Nov 21, 2009, 1:23:22 AM11/21/09
to wpf-di...@googlegroups.com
It's the Assert that fails. I.e when the test is run alone, the button's text is set properly. When all tests are run together, the new text is not set properly at the time of the Assert.

Cheers,
Laurent
--
Sent from mobile

>privateconst string NewContent = "New
>content";
>privateButton _button;
>
>[TestMethod]
>publicvoid >TestDispatchingToUiThread()


>{
> _button = new Button
> {
> Content = "Content1"
> };
>
> var >manualEvent = new ManualResetEvent(false);
>
> var >thread = new Thread(()=>
> {
> Thread.Sleep(500);
> DispatcherExtensions.CheckBeginInvokeOnUi(() =>
>AccessMethodOnUiThread(NewContent));
>
>manualEvent.Set();
> });
>
> DispatcherExtensions.Initialize();
>
> thread.Start();
>
> manualEvent.WaitOne(30000);
>
> DoEvents();
> Assert.AreEqual(NewContent,
>_button.Content);
>}
>

>privatevoid >AccessMethodOnUiThread(string newContent)
>{
> _button.Content = newContent;
>}
>
>publicvoid DoEvents()


>{
> var >frame = new DispatcherFrame();
> Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
> new DispatcherOperationCallback(ExitFrames),
>frame);
> Dispatcher.PushFrame(frame);
>}
>

>publicobject ExitFrames(object f)


>{
> ((DispatcherFrame)f).Continue
>= false;
> return null;
>}
>
>
>--
>Laurent Bugnion [Microsoft MVP,
>MCP]
>Blog: http://blog.galasoft.ch
>Web: http://www.galasoft.ch
>Support children in Calcutta: http://www.calcutta-espoir.ch
>

Laurent Bugnion

unread,
Nov 21, 2009, 1:24:31 AM11/21/09
to wpf-di...@googlegroups.com
Also, the DispatecherExtensions method i call does a BeginInvoke on the main thread.

Daniel Vaughan

unread,
Nov 21, 2009, 5:52:34 AM11/21/09
to WPF Disciples
I believe it's because your manualEvent.Set() call should occur on the
same thread as the DispatcherExtensions invokes AccessMethodOnUiThread
(NewContent)

Try changing it to something like
DispatcherExtensions.CheckBeginInvokeOnUi(() => {
AccessMethodOnUiThread(NewContent);
manualEvent.Set();
});

Daniel Vaughan

unread,
Nov 21, 2009, 5:55:04 AM11/21/09
to WPF Disciples
Furthermore, there are no guarantees that
DispatcherExtensions.CheckBeginInvokeOnUi(() =>
AccessMethodOnUiThread(NewContent));
will occur before DoEvents() is called. My guess is that in fact it's
not being called.


On Nov 21, 7:24 am, "Laurent Bugnion" <laur...@galasoft.ch> wrote:

Daniel Vaughan

unread,
Nov 21, 2009, 5:57:57 AM11/21/09
to WPF Disciples
To be more accurate, what I meant was that there are no guarantees
that AccessMethodOnUiThread(NewContent) will occur before DoEvents()
is called.

Laurent Bugnion, GalaSoft

unread,
Nov 21, 2009, 8:18:11 AM11/21/09
to wpf-di...@googlegroups.com
Calling manualEvent.Set() on the main thread did not solve the issue, same
behavior exactly. What you say about DoEvents being probably called before
AccessMethodOnUiThread makes sense, of course. I am just curious to know why
it is consistently different when I run one test, or all tests. I think I
will probably have to give up there.

Note that in Silverlight, it works differently: Since the DoEvents method
doesn't work there, what I do is BeginInvoke a "Verify" method which calls
the Assert. This way the method setting the Button's content is enqueued
before the Verify method, and they are executed in that order. Unfortunately
this doesn't work in MSTests, because of the way that tests are executed. In
Silverlight, the test runner is a standard Silverlight application and runs
the tests in a linear manner. On the other hand, when I call BeginInvoke on
the MSTest runner on the main thread, the test just terminates without
executing the queue.

Honestly I wish the MSTest runner was more like the Silverlight test runner,
it is really much easier to program against. I guess I could try in NUnit to
see how it behaves, but I don't really have the motivation right now...

Anyway, thanks for the help and cheers,
Laurent

Daniel Vaughan

unread,
Nov 21, 2009, 8:45:34 AM11/21/09
to WPF Disciples
Laurent,
Out of interest would you please post your DispatcherExtensions source
and in particular the DispatcherExtensions.CheckBeginInvokeOnUi.

On Nov 21, 2:18 pm, "Laurent Bugnion, GalaSoft" <laur...@galasoft.ch>
wrote:

Laurent Bugnion, GalaSoft

unread,
Nov 21, 2009, 9:25:31 AM11/21/09
to wpf-di...@googlegroups.com

OK however note the following:

 

·         This is an early version, so any comment is really appreciated

·         I tried to find an exact same syntax in Silverlight and WPF, which explains why I need to initialize the DispatcherExtensions prior to usage (or else I have issues in Silverlight).

·         Make sure that DispatcherExtensions.Initialize is called on the UI thread. In WPF, this can be done in the static App constructor. In Silverlight, this can be done in Application_Startup right after the RootVisual has been initialized.

 

Again, comments are welcomed.

 

    public static class DispatcherExtensions

    {

        public static void Initialize()

        {

            if (UiDispatcher != null)

            {

                return;

            }

 

#if SILVERLIGHT

            UiDispatcher = Application.Current.RootVisual.Dispatcher;

#else

            UiDispatcher = Dispatcher.CurrentDispatcher;

#endif

        }

 

        public static Dispatcher UiDispatcher

        {

            get;

            private set;

        }

 

        public static void CheckBeginInvokeOnUi(Action action)

        {

            if (UiDispatcher.CheckAccess())

            {

                action();

            }

            else

            {

                UiDispatcher.BeginInvoke(action);

Daniel Vaughan

unread,
Nov 21, 2009, 1:46:45 PM11/21/09
to WPF Disciples
Ok, call me Mr Obvious, but is anything modifying the value of the
_button field? Did you try making it local, and passing it to the
AccessMethodOnUiThread?

In SL using a dispatcher only allows for async. To get around this I
use a DispatcherSynchronizationContext. I include this because I
thought you might be about to grab something from it. If not,
nevermind.

/// <summary>
/// Singleton class providing the default implementation
/// for the <see cref="ISynchronizationContext"/>, specifically for
the UI thread.
/// </summary>
public class UISynchronizationContext : ISynchronizationContext
{
DispatcherSynchronizationContext context;
Dispatcher dispatcher;

#region Singleton implementation

static readonly UISynchronizationContext instance = new
UISynchronizationContext();

/// <summary>
/// Gets the singleton instance.
/// </summary>
/// <value>The current.</value>
public static ISynchronizationContext Instance
{
get
{
return instance;
}
}

UISynchronizationContext()
{
try
{
dispatcher = HtmlPage.Document.Dispatcher;
context = new DispatcherSynchronizationContext(dispatcher);
}
catch (Exception)
{
/* This occurs if called from a non-ui thread. Ignore. */
//Debug.WriteLine("Unable to get root visual in private
constructor of UISynchronizationContext." + ex);/* TODO: Make
localizable resource. */
}

}

#endregion

public void Initialize()
{
EnsureInitialized();
}

[MethodImpl(MethodImplOptions.Synchronized)]
void EnsureInitialized()
{
if (dispatcher != null && context != null)
{
return;
}

try
{
dispatcher = HtmlPage.Document.Dispatcher;
context = new DispatcherSynchronizationContext(dispatcher);
}
catch (InvalidOperationException)
{
throw new ConcurrencyException("Initialised called from non-UI
thread."); /* TODO: Make localizable resource. */
}
}

public void Initialize(Dispatcher dispatcher)
{
ArgumentValidator.AssertNotNull(dispatcher, "dispatcher");
this.dispatcher = dispatcher;
context = new DispatcherSynchronizationContext(dispatcher);
}

public void InvokeAsynchronously(SendOrPostCallback callback, object
state)
{
ArgumentValidator.AssertNotNull(callback, "callback");
EnsureInitialized();

context.Post(callback, state);
}

public void InvokeAsynchronously(Action action)
{
ArgumentValidator.AssertNotNull(action, "action");
EnsureInitialized();

if (dispatcher.CheckAccess())
{
action();
}
else
{
dispatcher.BeginInvoke(action);
}
}

public void InvokeSynchronously(SendOrPostCallback callback, object
state)
{
ArgumentValidator.AssertNotNull(callback, "callback");
EnsureInitialized();

context.Send(callback, state);
}

public void InvokeSynchronously(Action action)
{
ArgumentValidator.AssertNotNull(action, "action");
EnsureInitialized();

if (dispatcher.CheckAccess())
{
action();
}
else
{
context.Send(delegate { action(); }, null);
}
}

public bool InvokeRequired
{
get
{
EnsureInitialized();
return !dispatcher.CheckAccess();
}
}
}

I then leverage a bunch of Extension methods adapted from some work
Sacha did.

public static class DispatcherExtensions
{

#if !SILVERLIGHT
/// <summary>
/// A simple threading extension method, to invoke a delegate
/// on the correct thread if it is not currently on the correct
thread
/// which can be used with DispatcherObject types.
/// </summary>
/// <param name="dispatcher">The Dispatcher object on which to
/// perform the Invoke</param>
/// <param name="action">The delegate to run</param>
/// <param name="priority">The DispatcherPriority for the invoke.</
param>
public static void InvokeIfRequired(this Dispatcher dispatcher,
Action action, DispatcherPriority priority)
{
if (!dispatcher.CheckAccess())
{
dispatcher.Invoke(priority, action);
}
else
{
action();
}
}
#endif

/// <summary>
/// A simple threading extension method, to invoke a delegate
/// on the correct thread if it is not currently on the correct
thread
/// which can be used with DispatcherObject types.
/// </summary>
/// <param name="dispatcher">The Dispatcher object on which to
/// perform the Invoke</param>
/// <param name="action">The delegate to run</param>
public static void InvokeIfRequired(this Dispatcher dispatcher,
Action action)
{
if (!dispatcher.CheckAccess())
{
#if SILVERLIGHT
dispatcher.BeginInvoke(action);
#else
dispatcher.Invoke(DispatcherPriority.Normal, action);
#endif
}
else
{
action();
}
}

/// <summary>
/// A simple threading extension method, to invoke a delegate
/// on the correct thread if it is not currently on the correct
thread
/// which can be used with DispatcherObject types.
/// </summary>
/// <param name="dispatcher">The Dispatcher object on which to
/// perform the Invoke</param>
/// <param name="action">The delegate to run</param>
public static void InvokeInBackgroundIfRequired(this Dispatcher
dispatcher, Action action)
{
if (!dispatcher.CheckAccess())
{
#if SILVERLIGHT
dispatcher.BeginInvoke(action);
#else
dispatcher.Invoke(DispatcherPriority.Background, action);
#endif
}
else
{
action();
}
}

/// <summary>
/// A simple threading extension method, to invoke a delegate
/// on the correct thread asynchronously if it is not currently on
the correct thread
/// which can be used with DispatcherObject types.
/// </summary>
/// <param name="dispatcher">The Dispatcher object on which to
/// perform the Invoke</param>
/// <param name="action">The delegate to run</param>
public static void InvokeAsynchronouslyInBackground(this Dispatcher
dispatcher, Action action)
{
#if SILVERLIGHT
dispatcher.BeginInvoke(action);
#else
dispatcher.BeginInvoke(DispatcherPriority.Background, action);
#endif
}

}

On Nov 21, 3:25 pm, "Laurent Bugnion, GalaSoft" <laur...@galasoft.ch>
wrote:
> OK however note the following:
>
> .         This is an early version, so any comment is really appreciated
>
> .         I tried to find an exact same syntax in Silverlight and WPF, which
> explains why I need to initialize the DispatcherExtensions prior to usage
> (or else I have issues in Silverlight).
>
> .         Make sure that DispatcherExtensions.Initialize is called on the UI

Sacha Barber

unread,
Nov 22, 2009, 5:32:22 AM11/22/09
to wpf-di...@googlegroups.com
Daniel

Nice work on the DispatcherSynchronizationContext, but call me dumb but I thought to use a SynchonizationContext, you had to do something like


UISynchronizationContext.Instance.InvokeAsynchronously( 
()=> 
{
.....
});


I could not see you doing this, am I missing something?
And when you say this, what do you mean exactly ?

In SL using a dispatcher only allows for async. To get around this I
use a DispatcherSynchronizationConte
xt.


And you know your extension methods you are using the native Dispatcher and not your fancy DispatcherSynchronizationContext, perhaps I do not understand enough about SynchronizationContexts as I would normally just use the dispatcher or one of my original extension methods. I did not quite understand, you either use the Dispatcher itself or use your DispatcherSynchronizationContext right? Thats why the methods such as 

InvokeAsynchronously
InvokeSynchronously

Are public after all.

Could you clarify these few points.

--
Sacha Barber
sacha....@gmail.com

Daniel Vaughan

unread,
Nov 22, 2009, 6:33:28 AM11/22/09
to WPF Disciples
Sacha,
You asked me what I meant by “In SL using a dispatcher only allows for
async.”
What I meant was that in Silverlight 2 and 3, the
System.Windows.Threading.Dispatcher class does not contain an Invoke
method. Only BeginInvoke. That’s why I use the
DispatcherSynchronizationContext.

I can see why it looks a bit odd. I haven’t integrated the
UISynchronizationContext into the extension methods because it’s in a
Silverlight project, and the extension methods are in a Desktop
project. Some further work needs to be done to target both
environments in the same way. I see that was my intention with the use
of the ISynchronizationContext.


On Nov 22, 11:32 am, Sacha Barber <sacha.bar...@gmail.com> wrote:
> Daniel
>
> Nice work on the DispatcherSynchronizationContext, but call me dumb but I
> thought to use a SynchonizationContext, you had to do something like
>
> UISynchronizationContext.Instance.InvokeAsynchronously(
> ()=>
> {
> .....
>
> });
>
> I could not see you doing this, am I missing something?
> And when you say this, what do you mean exactly ?
>
> *In SL using a dispatcher only allows for async. To get around this I
> use a DispatcherSynchronizationConte**xt.*
> *
> *
> *
> *
> *And you know your extension methods you are using the native Dispatcher and
> not your fancy DispatcherSynchronizationContext, perhaps I do not understand
> enough about SynchronizationContexts as I would normally just use the
> dispatcher or one of my original extension methods. I did not quite
> understand, you either use the Dispatcher itself or use
> your DispatcherSynchronizationContext right? Thats why the methods such as *
>
> InvokeAsynchronously
> InvokeSynchronously
>
> Are public after all.
>
> Could you clarify these few points.
> *
> *
> ...
>
> read more »

Sacha Barber

unread,
Nov 22, 2009, 6:49:56 AM11/22/09
to wpf-di...@googlegroups.com
Ah ok I see. I have not worked with SL enough to know that. Thanks for that extra info, just sent you a question on your latest Calcium post too, would really appreciate an answer on that one.

Cheers buddy
--
Sacha Barber
sacha....@gmail.com
Reply all
Reply to author
Forward
0 new messages