1

In my WPF program it took huge processing time and freezing for long time.

so I decided to use background worker and process it in background.

but it does not work. through debug, the program stop at Render3D(). It does not throw exception. Its like when you put return.

In other word it does nothing after reaching Render3D() and will just return.

(I don't say it will return Because im not sure but the behavior is same as return)

    private readonly BackgroundWorker backgroundWorker = new BackgroundWorker();
    private AssetDeclaration _assetDeclaration = new AssetDeclaration();

    public MainWindow()
    {
        backgroundWorker.DoWork += backgroundWorker1_DoWork;
        backgroundWorker.ProgressChanged += backgroundWorker1_ProgressChanged;
        backgroundWorker.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
        InitializeComponent();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i = 1; i <= 1000; i++)
        {
            if (!((BackgroundWorker)sender).CancellationPending)
            {
                Render3D(); // will return at this point. (why?) or waiting for something to start?
                ((BackgroundWorker)sender).ReportProgress(i);
            }
            else
            {
                e.Cancel = true;
                break;
            }
        }
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        MessageBox.Show("Done!");//will show message box instant.
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        ProgressBar1.Value = e.ProgressPercentage;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        //...Some work here before starting Hard job!
        //...From now i want to start heavy process in background.
        //...with report to progress bar at same time.
        backgroundWorker.RunWorkerAsync(100);
    }

Render3D() works fine without Background processing but will freeze for some time.

Render3D() is in Partial class of MainWindow .because there are lots of methods so i decided to separate them.

Also how can I use ReportProgress outside backgroundWorker1_DoWork . for example in Render3D().

Last thing : i want to know how to show the user how much of process is done.

Solved!:

The problem was because i set Viewport3D inside Render3D()

I separated it from Render3D and problem got fixed. thanks to Henk Holterman for the right answer.

It seems some tasks cant be done in another Thread. with the Error report i find out that the invalid task is setting Viewport3D properties.

this tasks must be done in Main thread.

below is invalid Code that made background worker stop functioning.

DefineCamera();
Viewport.Children.Add(model); // Must be run in Main thread.

And this Part.

    private void DefineCamera()
    {
        PerspectiveCamera camera = new PerspectiveCamera
        {
            FieldOfView = 60
        };

        PositionCamera(camera);
        Viewport.Camera = camera; // Must be run in Main thread.
    }
Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
M.kazem Akhgary
  • 18,645
  • 8
  • 57
  • 118
  • You don't have a Completed event. Add it and process the incoming `e.Error`. Or just add a try/catch to DoWork. You are now ignoring the exception that could tell you what went wrong. – H H Jun 07 '15 at 21:05
  • "after i added RunWorkerCompleted" Ok, now make it report `e.Error`. – H H Jun 07 '15 at 21:13
  • Check [my answer here](http://stackoverflow.com/a/4807200/60761) for a canonical Completed event. – H H Jun 07 '15 at 21:15
  • oh yes it shown Error. System.InvalidOperationException the calling thread cannot access this object because a different thread owns it @HenkHolterman – M.kazem Akhgary Jun 07 '15 at 21:15
  • 1
    OK, Render3D() is not suitable for running on a Thread. End of exercise. – H H Jun 07 '15 at 21:17
  • I'm with henk on this, what is the reason for moving render to a separate thread - anything render based should be on the UI thread? – kidshaw Jun 07 '15 at 21:21
  • what? . whitch part is not suitable? i have wrote it all my self. i used xml reader ,vector3d,vector ,point3d, color,quaternion and some other classes witch part is not suitable i did not understand and final model will render into viewport3d @HenkHolterman – M.kazem Akhgary Jun 07 '15 at 21:21
  • 1
    We can't comment on / analyze code we can't see. The ViewPort won't be safe to use. Maybe you can split the GUI / non-GUI code, I can't tell. – H H Jun 07 '15 at 21:23
  • i dont know. without this program will freeze and not-respond for moment i want to prevent this. @kidshaw – M.kazem Akhgary Jun 07 '15 at 21:24
  • 1
    +1 thanks. ill try to split non GUI part. drawing model only takes milliseconds .so this may fix the problem @HenkHolterman – M.kazem Akhgary Jun 07 '15 at 21:29
  • you were Right Henk. Problem got Fixed. the problem was because i set ViewPort inside Render3D() and thats why i got this exception :the calling thread cannot access this object because a different thread owns it. please answer this this question because you find the answer. @HenkHolterman – M.kazem Akhgary Jun 07 '15 at 23:48
  • Does it run if you comment out the call to Render3D()? If so, Render3D() expects to be run on the Main thread, rather than a worker thread: in which case you cannot fork that operation to a worker thread. – Phillip Ngan Jun 08 '15 at 02:39
  • this question is already answered by HenkHolterman in comments but i cant mark it as answer! yes . i moved out the part that initialize ViewPort3D from render3D(). so the model gets ready in render3D() and will draw in another method. @PhillipNgan – M.kazem Akhgary Jun 08 '15 at 07:34

3 Answers3

4

First of all, you had trouble finding the error.

... the program stop at Render3D(). It does not throw exception. Its like when you put return.

What actually happened was that an exception was thrown by your method and was captured by the Backgroundworker. It is transferred to the Completed event but you do have to act on it there.

private void worker_Completed(object sender, RunWorkerCompletedEventArgs e) 
{
  // check error, check cancel, then use result
  if (e.Error != null)
  {
     // handle the error
  }
  else if (e.Cancelled)
  {
     // handle cancellation
  }
  else      
  {         
      // use the result(s) on the UI thread
  }    
  // general cleanup
}

Failing to look at either e.Error or e.Result is the same as having an empty catch{} block in your program.

And with error handling in place we then have

oh yes it shown Error. System.InvalidOperationException the calling thread cannot access this object because a different thread owns it

This indicates that your Render3D() still interacts with the GUI somewhere.

The basic advice is to separate all the calculation (and I/O, database) work from the UI work. You can run the CPU bound and I/O bound cod in a thread but the GUI is single threaded, you can only interact with it from the main Thread.

H H
  • 263,252
  • 30
  • 330
  • 514
0

In the world of WPF, unlike Windows Forms that you were used to, you should consider Dispatcher. To do this, you have to import System.Windows.Threading

    private void ThreadTask()
    {
        Thread.Sleep(TimeSpan.FromSeconds(5));

        this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                    (ThreadStart)delegate()
                    {
                        //Do some heavy task here...
                    });
    }

Quick Update

In order to run the thread from a button click or whatever, any function, add this line of code:

    Thread thread = new Thread(new ThreadStart(ThreadTask));
    thread.Start();

This line of code is equivalent to BackgroundWorker.RunWorkerAsync();

  • 1
    Can someone explain why the down vote? I've used that before without any problem! – Ahmad N. Chatila Jun 07 '15 at 21:16
  • 1
    This makes no sense. Even when we ignore the Sleep(), the "heavy task" is sent back to the main Gui thread. No gain whatsoever. Just a confusing detour. – H H Jun 07 '15 at 21:20
  • @HenkHolterman I'm not the only one to say thats the working solution. You can check the C# WPF documentation (I forgot what its name was). – Ahmad N. Chatila Jun 07 '15 at 21:25
  • 1
    It will work but it won't solve your freezing issue. – H H Jun 07 '15 at 21:27
  • 1
    @HenkHolterman I've been living with the dispatcher almost two years without any problem. I've used it for database app development (which is something that demands heavy tasks). – Ahmad N. Chatila Jun 07 '15 at 21:31
  • When you do the Db stuff in the place of the Sleep() then it's sort of OK. When you do it in the BeginInvoke then you've been doing it wrong all that time. – H H Jun 07 '15 at 21:33
  • 1
    @user3104111: Like Henk says, you are completely ignoring the source of OP's problem. This has nothing to do with using BackgroundWorker or not. And if we're going to talk about best practices, then using Tasks and async would be superior to what you are suggesting, not to mention that you are creating individual threads rather than using the standard thread pool, and I could go on... – sstan Jun 07 '15 at 21:35
  • 1
    i tried it and it freeze again.as Henk said i think if i be able to split non GUI part then i can fix this problem ill work on it. – M.kazem Akhgary Jun 07 '15 at 21:39
-2

I would highly recommend using async/await. This feature was introduced in .NET 4.5, and is used to shift work off the main WPF GUI thread to make the application fast and responsive.

Essentially, the rule is to push any calculations which do not interact with GUI onto a background thread using a combination of Task.Run and async/await. Together with Dispatcher.Invoke, you don't really need anything else.

For example, a slow data call that might fetch data from the database could be pushed onto a background thread, so the application does freeze while it waits for the SQL to execute.

I've used this to make the applications that I write fast, responsive and snappy.

Contango
  • 76,540
  • 58
  • 260
  • 305