views:

216

answers:

2
+2  Q: 

WPF multithreading

I'm scratching my head in trying to get the multithreading to work as I want in WPF

I have an object (with a singleton) called Manager which does alot of processing and lookups. I want it to run in a separate thread from the UI, The UI will call methods on Manager to do the processing and fire events that the UI should react to

I'm converting this app from windows forms where I dont think this was a problem since the textbox event was automatically fired in a different thread. Not so in WPF where everything seems to stay on the UI thread.

How do I make an object "live" in a different thread and then how do I call its methods. I tried spawning it to a new thread using this in the Window constructor

        Thread managerThread = new Thread(new ThreadStart(ManagerStartingPoint));
        managerThread.SetApartmentState(ApartmentState.STA);
        managerThread.IsBackground = true;
        managerThread.Start();

and:

private void ManagerStartingPoint()
    {
        ManagerSingleton.Manager = new Manager();
        MediatorSingleton.Mediator = new Mediator();
    }

On the textbox textchanged event I call Manager.SetItemText(e.Value) which logically should be called on the new thread right? Still when I type and the event triggers the UI "stutters" and typing in the textbox is affected. Do I need to call the method asynchronously or something?

+3  A: 

All you're doing is creating a new object on another thread. After you create the object, the thread completes execution and becomes idle.

Object instances don't "live" on threads. An instance is just a pointer to a structure in memory. Just because you create an instance in thread A doesn't mean that all methods in that instance will run on thread A.

What you want to do is create a class that you can call from any thread but which uses its own internally managed thread (or some such construct) to perform execution.

Your "manager" should encapsulate a thread, or use the ThreadPool, to perform your "processing." When done, the manager will have to communicate back to your UI (using the Dispatcher to marshall this communication back onto the UI thread) indicating it has completed execution.

Will
I see, seems my understanding of multithreading is a bit limited :) So the thread that calls a method decides which thread the resulting event is fired on? and if one object subscribes to an event on another and that event is triggered then that subscribed method also will run in the originating thread?In that case the best way should be to call all methods and properties on Manager through a asynchronous methodinvoker right?
MattiasK
Your best bet is to read up on threading. I'd suggest grabbing a copy of CLR Via C# by Jeff Richter. You will not be disappointed. A "Thread" is an execution context--it is where code is executed by a CPU. Execution goes from line to line. Execution doesn't magically jump from thread to thread; without a thread there is no execution. Execution of code doesn't exist outside or independently of a thread. If a thread's execution hits an event it executes the thread subscriber's handlers one by one.
Will
When a thread starts executing a method in your manager instance, you want that thread to pass a reference to another method to another thread (e.g., ThreadPool.EnqueueUserWorkItem), which doesn't block the current thread, allowing it to return from the method call. The TP will eventually get CPU time and start executing your work item on a different thread. Man, this is hard to describe. Unless you understand how threading and code execution works you should learn up about it before trying to use multithreading.
Will
I guess I was temporarily confused due to the fact that if you create a window with the same approach that window is infact run in a new thread (if you call Dispatcher.Run())). Anyone know why that is?
MattiasK
@matt The UI thread is a special beast. The UI thread runs a loop called the "message pump" which interacts with the operating system to handle UI events and messages from the operating system (like WM_PAINT). This message pump keeps your UI responsive to system events like mouse clicks. If you click a button, the pump gets the relevant message, maps it to the button, and executes the event handler. Do everything in your UI and the message pump thread is doing your work and not running the message pump, which causes your UI to become "non-responsive."
Will
Yeah, I'm aware of the message pump and that Dispatcher.Run() starts a new one for the window.. but I'm still a bit unsure on exactly how the window get's associated to a new thread, or isnt it a separate thread? just a separate pump?
MattiasK
I can't speak for Dispatcher.Run, but old school windows forms used Application.Run(new MainForm()) to associate the main form with the pump. There's one pump per app, I believe. Other than that, I'm not sure how the pump works. Magic or something. Like how thermoses keep warm stuff warm and cold stuff cold.
Will
Hehe, oh well, atleast I'm a bit wiser now about threading and the parallel.net stuff seems to work well, thanks everyone!
MattiasK
+4  A: 

You are just creating these objects in the background; that doesn't really make a difference. You need to call your methods in the background:

public YourTextBox_TextChanged(...) {
    var bw = new BackgroundWorker();

    bw.DoWork += (sender, args) => {
        // do your lengthy stuff here -- this will happen in a separate thread
        Manager.SetItemText(e.Value)
    }

    bw.RunWorkerCompleted += (sender, args) => {
        if (args.Error != null)  // if an exception occurred during DoWork,
            MessageBox.Show(args.Error.ToString());  // do your error handling here

        // Do whatever you want to do after the SetItemText has completed.
        // We are back in the UI thread here.
        ...
    }

    bw.RunWorkerAsync(); // start the background worker
}

PS: Make sure your Manager.SetItemText method is thread-safe! Using background threads, it is quite possible that multiple instances of Manager.SetItemText run in parallel (if a second TextChanged event arrives before the first SetItemText has been completed).

Heinzi
+1 - Exactly what I was going to post.
Nate Bross
I can't do that due do the nature of the application. I don't just call methods that need to runs in the background one at the time in the background. The UI can fire off lots off calls to Manager almost simultaneously and which then triggers the appropriate events.
MattiasK
Sorry I misread, yes I could spawn a new backgroundworker for each call but seems that should have some uneccesary overhead, wouldnt an asynchronous methodinvoker or something from parallel.net be better fitted?
MattiasK
@Mattias: That's a good question; I honestly don't know how the overhead of BackgroundWorker compares to other methods. I guess something like `ThreadPool.QueueUserWorkItem((state) => Manager.SetItemText(e.Value))` would work as well. You just need to remember to use the Dispatcher if you want to do anything in the UI after completion.
Heinzi
I opted to go with: Task.Factory.StartNew(() => { Manager.SetItemText(e.Value); });4.0 only though
MattiasK