19

I am converting a chat parser for a game i play that i wrote in c# winforms over to wpf, mainly just to get a better handle on MVVM and wpf. Here is a run down of how i have my project set up

View: For now its just a simple ListBox with ItemSource bound to my viewmodels observable chat collection

Model: I have multiple characters that can be logged in at one time and each character has a chat class. The chat class starts a background worker that grabs and next line of chat from the game and fires off an event called IncomingChat with this line.

public event Action<Game.ChatLine> IncomingChat;

I'm using a background worker to fire an event in my backgroundworkers progresschaged event because when i was using a timer i kept getting a threading issue. At first i corrected this by changing my Timer to a DispatchTimer, but this didn't seem right to me to have a DispatchTimer in my model.

ViewModel: Since i have multiple characters i am creating multiple ChatViewModels. I pass a character into the ChatViewModels constructor and subscribe to the Chat event. I create a ObservableColleciton to hold my chat lines when this event is received. Now I'm receiving a threading issue on my viewModel when trying to add the line i receive from my chat event to my observablecollection.

I got around this by making my viewmodels incoming chat event handler look like so

public ObservableCollection<Game.ChatLine) Chat {get; private set;}

void Chat_Incoming(Game.ChatLine line)
{
  App.Current.Dispatcher.Invoke(new Action(delegate
  {
    Chat.Add(line)
  }), null);
}

This doesn't feel right to me though. Although it works, using Dispatcher in my viewmodel like this seems out of place to me.

poco
  • 2,935
  • 6
  • 37
  • 54

3 Answers3

40

Although it works, using Dispatcher in my viewmodel like this seems out of place to me.

This isn't a completely unreasonable approach, and is the approach that many people take. Personally, if you're using WPF (or Silverlight 5), and have access to the TPL, I prefer to use the TPL to handle this.

Assuming your ViewModel is constructed on the UI thread (ie: by the View, or in response to a View related event), which is the case nearly always IMO, you can add this to your constructor:

// Add to class:
TaskFactory uiFactory;

public MyViewModel()
{
    // Construct a TaskFactory that uses the UI thread's context
    uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
}

Then, when you get your event, you can use this to marshal it:

void Chat_Incoming(Game.ChatLine line)
{
    uiFactory.StartNew( () => Chat.Add(line) );
}

Note that this is slightly different than your original, since it's no longer blocking (this is more like using BeginInvoke instead of Invoke). If you need this to block until the UI finishes processing the message, you can use:

void Chat_Incoming(Game.ChatLine line)
{
    uiFactory.StartNew( () => Chat.Add(line) ).Wait();
}
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 1
    The SynchronizationContext is the one to use. This pattern is used throughout, so your threading code can be written generically and still be available whether you're doing winforms, wcf, wpf, wf or ASP.NET (I'm assuming the last one). –  Sep 19 '11 at 17:27
  • 1
    @Will: The OP was using WPF (in the text), but yes, this will work for any tech, which is why I like it so much... – Reed Copsey Sep 19 '11 at 17:30
  • 1
    +1, I didn't realize that TPL had a scheduler that could use the SynchronizationContext. My code has still been using the SynchronizationContext Post/Send directly. – Dan Bryant Sep 19 '11 at 17:37
  • @Dan: It's very useful, especially when mixed with continuations (see: http://reedcopsey.com/2010/04/19/parallelism-in-net-part-17-think-continuations-not-callbacks/) – Reed Copsey Sep 19 '11 at 17:38
  • @Reed, Exactly; I've been registering continuations that call SynchronizationContext.Post within the continuation body. – Dan Bryant Sep 19 '11 at 17:40
  • @Dan: See my post (linked above) for a much nicer solution, then ;) – Reed Copsey Sep 19 '11 at 17:42
  • @ReedCopsey: I just watched Anders at //Build, TPL looks *so* much easier with the `async` and `await` keywords... –  Sep 19 '11 at 19:11
  • @Will: Yeah - C# 5 async support will dramatically improve a lot of code. That being said, it wouldn't help too much *in this case*, at least not until you were back in the main context, as the incoming event isn't something you'd call await on.. Making async work with this would require a bit of a redesign... – Reed Copsey Sep 19 '11 at 19:19
  • @ReedCopsey, thanks for the input, I guess its time I read up on TPL. – poco Sep 19 '11 at 21:01
  • The resulting DispatcherSynchronizationContext will always process message at DispatcherPriority.Normal (9), which is higher than DispatcherPriority.Input (5), so a Background thread flooding the Dispatcher with status updates will prevent the user from clicking the cancel button. – springy76 Apr 30 '14 at 16:01
  • @springy76 Yes, but I'd hope that a "chat" context wouldn't flood with enough messages to prevent input completely... – Reed Copsey Apr 30 '14 at 17:48
0

A View Model is a good place to do a Thread Synchronization. Remove the DispatcherTimer from your model and let the VM handle it.

Maxim V. Pavlov
  • 10,303
  • 17
  • 74
  • 174
  • There is no DispatcherTimer in the above - the incoming event is obviously not a DispatcherTimer event, since it's happening on a background thread. – Reed Copsey Sep 19 '11 at 16:18
  • Yes, but as I read from the question body, poco did use a DispatcherTimer in the model. He didn't explicitly mention that he removed it later, from what I see. – Maxim V. Pavlov Sep 19 '11 at 16:27
0

I love Reed's answer, and agree with your concerns that something isn't right with your use of the Dispatcher. Your VM references App, which is, in my mind, a reference to a UI artifact (or control). Use Application instead or, better yet, inject the correct Dispatcher instance into your VM, which avoids the need to instantiate your VM in the UI thread.

RMart
  • 548
  • 1
  • 5
  • 20