4

I'm trying to test a class that uses CommandManager.RequerySuggested and noticed that calling CommandManager.InvalidateRequerySuggested does not fire RequerySuggested from my test. Is there a reason for this and how to I resolve this issue? Does the CommandManager require some initialization?

Code to reproduce the issue:

[Test]
public void InvalidateRequerySuggested_TriggersRequerySuggested()
{
    bool triggered = false;
    CommandManager.RequerySuggested += (s, a) => triggered = true;

    CommandManager.InvalidateRequerySuggested();
    Thread.Sleep(1000); // Just to be sure

    Assert.True(triggered); // Never true
}
larsmoa
  • 12,604
  • 8
  • 62
  • 85
  • Try converting the test into an Async test, I believe that you may need to return control to the `SynchronizationContext`, otherwise if you have 1000 RequerySuggested subscribers and 1000 InvalidateRequerySuggested, you would have a LOT of work on your hands. – Aron Jun 24 '13 at 07:05

3 Answers3

3

As stated on the msdn here under remarks, CommandManager.RequerySuggested only holds a weak event reference. In your unit test the lambda expression is being garbage collected.

Try the following:

bool triggered;
EventHandler handler = (s, e) => triggered = true;
CommandManager.RequerySuggested += handler;
CommandManager.InvalidateRequerySuggested();
GC.KeepAlive(handler);
Assert.IsTrue(triggered);

Update

From some further investigation, I believe I have pinpointed the problem.

CommandManager.InvalidateRequestSuggested() uses the current dispatcher to raise the event asynchronously.

Here is a solution:

bool triggered;
EventHandler handler = (s, e) => triggered = true;
CommandManager.RequerySuggested += handler;
CommandManager.InvalidateRequerySuggested();

// Ensure the invalidate is processed
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new Action(() => { }));

GC.KeepAlive(handler);
Assert.IsTrue(triggered);
Lukazoid
  • 19,016
  • 3
  • 62
  • 85
  • The test still fails. And, as long as `handler != null`, `KeepAlive` is unnecessary - right? – larsmoa Jun 24 '13 at 07:58
  • The `KeepAlive` ensures the garbage collector does not collect `handler`. What I have pasted above would have definitely been a problem eventually (especially on memory starved systems). Your test must be failing for some other reason too though. I'll have a look into it in a bit :) – Lukazoid Jun 24 '13 at 08:09
  • So `EventHandler handler` does not keep a strong reference to `(s, e) => triggered = true`? – larsmoa Jun 24 '13 at 10:43
  • 1
    It does, but the garbage collector is able to collect local variables which are no longer used or referenced. So in this case, after the `CommandManager.RequerySuggested += handler;` line, `handler` is no longer used and is elligible for garbage collection thus the lambda is too. – Lukazoid Jun 24 '13 at 11:10
  • As long as `handler` is not null I still can't see the need for `KeepAlive`. The reference in `CommandManager` is weak, but the local reference is strong? – larsmoa Jun 24 '13 at 11:34
  • 1
    See [here](http://stackoverflow.com/a/8409492/921321) for another explanation. It may be in a debug compilation, without optimizations, this is a non-issue. – Lukazoid Jun 24 '13 at 11:58
  • I have updated my answer to include what I believe is the main problem you are encountering. – Lukazoid Jun 24 '13 at 12:07
1

Another possible reason for this behaviour: I've found that we need to subscribe to the RequerySuggested event using the same Dispatcher as the one InvalidateRequerySuggested is called on.

I've created a few objects on a non-UI thread that subscribed to this event, and the event did not get raised. Changing

CommandManager.RequerySuggested += HandleRequerySuggestedSuggested;

to

Application.Current.Dispatcher.Invoke((Action)(() => 
    CommandManager.RequerySuggested += HandleRequerySuggestedSuggested));

solved it for me.

Jens
  • 25,229
  • 9
  • 75
  • 117
0

Just keep a strong reference to your eventhandler

Alexis
  • 815
  • 6
  • 15