"An independent was changed while updating a dependent"

Mar 30, 2011 at 9:42 PM
Edited Mar 30, 2011 at 9:43 PM

So I'm trying to figure out how to add multi-select capability to my application. I decided to do it by adding an Independent<bool> to my viewmodel objects that represents the selection state of each item.

Now, my app has an UpdateListView that displays information about the currently selected item. When multiple items are selected, a "fake" model object is constructed that implements the same interface as a real model, but presents a special "combined view" based on the currently selected models. Then a standard ViewModel is created to wrap the fake model, which has its own IsSelected property that I set to true.

Unfortunately, this causes the assertion "An independent was changed while updating a dependent". A dependent reads the currently selected ViewModel, which causes the fake Model and ViewModel to be created, and the ViewModel's new Independent<bool> is set to true. This should be harmless because the Independent<bool> has no dependencies. Would it be possible to check for this case and not Assert? Or is something wrong with my design?

Mar 30, 2011 at 11:48 PM

You should be able to use the constructor to initialize the Independent&lt;bool&gt;, thus avoiding the issue. Another alternative is to use the raw Independent (not the generic version) and control the calls to OnSet yourself. Just don't call OnSet during initialization.

I could add a check to only throw if the Independet has Dependents, but that might mask some invalid code. Let me think about that one.

Mar 31, 2011 at 10:48 PM

Well, I was setting the selection state externally through a public IsSelected property. I could add an additional constructor to the ViewModel with an argument to control the initial selection state. However, since this fake ViewModel isn't shown anywhere where selection state matters, I don't actually need to mark it as selected. Still, I think on principle that the assertion (note that it's not an exception) should not be triggered in this case. Now, if the Independent had any dependents--even dependents that are unrelated to the one being updated--the assertion seems more justified, as it informs the coder of a likely design mistake.

May 4, 2011 at 11:46 PM
Edited May 4, 2011 at 11:51 PM

This assertion is biting me again... in a different way this time.

The problem now is that I have an Independent<string> which controls the server address that my program connects to. When the server address changes, my network code detects that event through a Dependent and begins a connection attempt to the server. In addition, I made an Independent<string> for the "connection status". Naturally this leads to the assertion when I give it the initial value (e.g. if the address is blank => set to "No server address selected" => ASSERT!).

Although it's technically an Independent, it's really "dependent" on the current state of the network connection. But of course I used Independent<string> so that my network code can set its Value and Update Controls will notice when the status changes.

How should I fix this? Would it help if I somehow tortured a Dependent into this role instead?

May 5, 2011 at 3:01 PM

I think the crux of your problem is that you are doing too much in the Dependent update method. I would have a Dependent<string> that simply gets the server address. Move all of your service calls to a background thread, and have it wait on an AutoResetEvent. Then you can add a handler for Invalidated that simply signals that event. In that background thread, use the Dependent<string> to get the server address, thus setting up the dependency again.

Now that you've moved the service call to the background thread, it is executed outside of the scope of the Dependent update method. It can set an Independent<string> connection status without causing the assertion.

May 5, 2011 at 5:13 PM
Edited May 5, 2011 at 5:19 PM

It makes some sense to do that in the normal case, since I already make the connection attempt on another thread. But if all I'm doing is setting Connection Status to "No server address selected", it seems like starting a new thread just to do that is overkill. Note that I can't simply use "BeginInvoke", since my network code does not take a dependency on WinForms/WPF.

Edit: I just realized I can do this as a simple one-liner:

new Thread(() => _connectionStatus.Value = "No server address selected.").Start();

I'm curious whether updating an Independent in a Dependent is fundamentally not possible for Update Controls, or whether the assert is more just intended to warn the user that they're probably Doing It Wrong. If it's the latter, it would be nice to be able to disable the assert in specific cases that I know are not a problem.

May 5, 2011 at 8:59 PM

I'm not sure why you need to initialize the connection status to "No server address selected" inside of a dependent. So, no, I wouldn't recommend spinning up another thread just to get around that limitation. Just assign the initial value when the Independent<string> is created, which should be well outside of any update methods. In fact, you could even use the constructor to set the initial value.

Setting an Independent inside of a Dependent is an indicator of a side-effect. The dependency tracking algorithm assumes that your code is side-effect free. If that assumption is incorrect, then the algorithm fails. I added this check when a member of my team introduced a subtle bug by writing side-effects into his update methods. It took several hours of his time before he even asked for my help, and then it took another hour of the two of us single-stepping through the code. Once I found the problem, I added this check. The bug lit up like a spot light.

In general there are two phases to an Update Controls application: ebb and flow. First, new information flows in from the user and sets a bunch of Independents. Then, updates ebb back out to the user as Dependents are calculated. You can always identify which phase a particular piece of code should be in. When you set an Independent while updating a Dependent, you are mixing the two phases. It's like a leaking heart valve, causing all sorts of trouble both down and upstream.

If you'd like to post some code on gist.github.com, I can take a look at it with you.

May 6, 2011 at 9:08 PM
Edited May 6, 2011 at 9:24 PM

I guess you're saying the dependency-tracking algorithm would malfunction somehow if an Independent is updated during update of a Dependent.

Since the user can change the connection address at any time, setting that at initialization isn't enough. I don't think I need to use github for this, the code's pretty simple:

// Derived class which is specific to my application.
// (uses GuiUpdateHelper which I posted earier; this code should be GUI-technology-
// agnostic but GuiUpdateHelper is "accidentally" WinForms-dependent because I don't
// know an easy technology-independent way to ensure OnGet() is called automatically.)
Action updateAddress = () => base.ConnectionAddress = _options.ServerAddress; _updater = new GuiUpdateHelper(true, updateAddress); // Base class with is application-agnostic and contains no dependents; // it doesn't know where the address comes from. Independent<string> _connectionStatus = new Independent<string>(); string _connectionAddress; public string ConnectionAddress { get { return _connectionAddress; } set { BeginConnect(value); } }
void BeginConnect(string newAddress) { if (_connectionAddress != newAddress) { _connectionAddress = newAddress; CloseConnection(); if (string.IsNullOrEmpty(newAddress)) { // Use a separate thread to avoid assertion new Thread(() => _connectionStatus.Value =
"No server address selected.").Start();
} else { _connectionThread = new Thread(ConnectionThread); _connectionThread.IsBackground = true; _connectionThread.Start(); } } }
May 9, 2011 at 7:55 PM

I'll find some code that demonstrates the pattern that I recommend and post a write up on it. So far, the closest code that I have posted on this pattern is Multi-threaded view model using Update Controls. I'll post something more specific shortly.

In short, the problem is the call to BeginConnect in the ConnectionAddress setter. You don't want to do any of that work directly in the setter. Just store the new connection address, which should be Independent. All of the assignments to _connectionStatus should take place in the background thread, even the "No server address selected" assignment.