views:

224

answers:

3

In my code I subscribe to an event that happens on a different thread. Every time this event happens, I receive an string that is posted to the observable collection:

    Dispatcher currentDispatcher = Dispatcher.CurrentDispatcher;
    var SerialLog = new ObservableCollection<string>();

    private void hitStation_RawCommandSent(object sender, StringEventArgs e)
    {
        string command = e.Value.Replace("\r\n", "");
        Action dispatchAction = () => SerialLog.Add(command);
        currentDispatcher.BeginInvoke(dispatchAction, DispatcherPriority.Render);
    }

The code below is in my view model (could be in the code behind, it doesn't matter in this case). When I call "hitstation.PrepareHit", the event above gets called a couple times, then I wait and call "hitStation.HitBall", and the event above gets called a couple more times.

    private void HitBall()
    {
        try
        {
            try
            {
                Mouse.OverrideCursor = Cursors.Wait;

                //prepare hit
                hitStation.PrepareHit(hitSpeed);

                Thread.Wait(1000);
                PlayWarning();

                //hit
                hitStation.HitBall(hitSpeed);
            }
            catch (TimeoutException ex)
            {
                MessageBox.Show("Timeout hitting ball: " + ex.Message);
            }
        }
        finally
        {
            Mouse.OverrideCursor = null;
        }
    }

The problem I'm having is that the ListBox that is bound to my SerialLog gets updated only when the HitBall method finishes. I was expecting seeing a bunch of updates from the PrepareHit, a pause and then a bunch more updates from the HitBall.

I've tried a couple of DispatcherPriority arguments, but they don't seem to have any effect.

A: 

You will probably not see updates from PrepareHit unless there is a context switch and there is no guarantee when context switch will occur (unless you block your current thread and that would increase the probability that a context switch will occur).

As iCe mentioned if you're doing this on the UI thread, then you might be blocking your UI. Does your UI block/freeze when you call Thread.Wait? If it doesn't then proceed reading below:

Update:
I can't think of anything that would not compromise concurrency... without compromising concurrency you could try to increase the priority of BeginInvoke: http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority.aspx

currentDispatcher.BeginInvoke(dispatchAction, DispatcherPriority.Send);

By the way, it looks like Render priority is lower than Normal priority:

Render The enumeration value is 7. Operations processed at the same priority as rendering.

DataBind The enumeration value is 8. Operations are processed at the same priority as data binding.

Normal The enumeration value is 9. Operations are processed at normal priority. This is the typical application priority.

Send The enumeration value is 10. Operations are processed before other asynchronous operations. This is the highest priority.

Lirik
Is there an alternative solution where I could see the items added to the list when they arrive?
Padu Merloti
@Padu, try to increase the priority of BeginInvoke
Lirik
just tried, same results. tomorrow I'll try iCe's suggestion
Padu Merloti
As a side note, there's good reason that Render priority is low: having Render priority lower than DataBind means that (thanks to the queued nature of the Dispatcher) you don't end up with multiple re-renders of a control when several databound properties change at once. Similarly, "Normal" operations are your general code operations - which would typically change the object your binding points to, and should thus all be allowed to complete before it's worth updating a binding. That's why it's even more important to push long-running batch-update stuff off of the UI thread in WPF.
Dan Puzey
Also: if your `HitTest` method is running on the Dispatcher thread, it's going to be allowed to complete before the next item on the Dispatcher queue interrupts it. So it doesn't matter what priority you're using if `HitTest` is on the wrong thread.
Dan Puzey
+3  A: 

I think you are blocking yourself.

The UI Thread is waiting at Thread.Wait, BeginInvoke sends the action to the dipatcher, however the UI Thread is busy waiting. That is why UI updates only get done after HitBall finishes, that is, when the UI Thread finishes processing.

To get around this, you should start the HitBall method's code in another thread, freeing the UI:

 private void HitBall() 
 { 
     try {

         Mouse.OverrideCursor = Cursors.Wait;
         Dispatcher dispatcher = Dispatcher.CurrentDispatcher;

         Action hitOperations = () =>
         {
             hitStation.PrepareHit(hitSpeed);

             Thread.Wait(1000);

             //Only needed if PlayWarning needs access to UI Thread
             dispatcher.Invoke(() => PlayWarning());

             hitStation.HitBall(hitSpeed);

             dispatcher.Invoke(() => Mouse.OverrideCursor = null);
          };

          //Free the UI from work, start operations in a background thread
          hitOperations.BeginInvoke(null, null);

      }
      catch (TimeoutException ex)
      {
          MessageBox.Show("Timeout hitting ball: " + ex.Message);
      }
 }

Also if the intended use of Thread.Wait(1000) was to wait for events to refresh the UI, with this implementation it is no longer needed.

iCe
Ok, that worked! But now I'm puzzled. I tried the same thing but instead of using Action and begininvoke, I've created and run a task, but the results weren't the same... why?
Padu Merloti
Sorry for the delay, how did you create the Task?Remember that with using only a Task you are not synchronizing with UI Thread, however with Invoke and BeginInvoke on the Dispatcher you are explictly synchronizing with UI. With BeginInvoke you do it in asynchronous manner.
iCe
+1  A: 

Could it be something as simple as needing to raise the PropertyChanged event on your ViewModel?

Doobi
I thought you didn't need to raise that event for ObservableCollections... am I wrong?
Padu Merloti
If what is changins is a property in the element inside an ObservableCollecction that element needs to implement INotifyPropertyChanged and raise that event. Extracted from: http://msdn.microsoft.com/en-us/magazine/dd252944.aspx"If you need to know if someone has changed a property of one of the items within the collection, you'll need to ensure that the items in the collection implement the INotifyPropertyChanged interface, and you'll need to manually attach property changed event handlers for those objects"
iCe