Skip to main content
gamedev.net
🔒 Locked

[.net] BackgroundWorker problem

Started by g0nzo Feb 16, 2007 at 7:49 AM 6 replies 5.1k views
Original Post
g0nzo
g0nzo
Hi! It's my first attempt to threading. I've got class Tracker that's constantly running in the background using BackgroundWorker. When it is cancelled I need to free a resource, but I've got problems with that. Here's my code:

class Tracker
{
  public void Run(object data)
  {
    try
    {
      cancel = false;
      BackgroundWorker bgWorker = null;
      if (data != null)
      {
        bgWorker = (BackgroundWorker)data;
      }

      cap = new CvCapture(-1, false);

      for (; ; )
      {          
        if (cancel == true) break;
        Thread.Sleep(1000);
        bgWorker.ReportProgress(0, new PointF(1.0f, 1.0f));
      }

      cap.Close(); // This line is never executed
    }
  }
}

The "cancel" member is set to true in RunWorkerCompleted event handler. The problem is that cap.Close(); is not called, when exiting the app. Where should it, so it would be executed when I exit my app? Thanks in advance
g0nzo
g0nzo
Once again I've asked too soon.

RunWorkerCompleted event is not triggered when app is quitting, so the "cancel" was never set to true. I've overridden OnClose event handler in the main form to set "cancel" to true and it looks like it works.

EDIT: Well, it doesn't work :)

[Edited by - g0nzo on February 16, 2007 9:08:28 AM]
Niksan2
Niksan2
The best way would be to hook up to the RunWorkerCompleted event, that will be called when the worker has finished/canceled or if an exception has been thrown in the worker, that would be the best way to handle it rather than polling outside the worker.

EDIT: oops, too late :D
g0nzo
g0nzo
Thanks anyway. You can still help, as it turned out that the app doesn't work :)

Here's the code I've added to the main form:
protected override void OnClosed(EventArgs e){  tracker.Cancel = true;  base.OnClosed(e);}

The strange thing is that when I set a breakpoint in OnClosed method, then when quitting the app, it breaks on this breakpoint and then breaks on another breakpoint set on the cap.Close(); line (see the first post) and the app quits fine.

But, when I don't set a breakpoint in OnClosed method or just run the app without debugging, the cap.Close(); line is not executed and the app is still running after quitting and I have to close it manually through Task Manager.

I have no idea why it works like that. Maybe it has something to do with Thread.Sleep()? As I've previously said I'm new to threading, so it's really a mystery to me.

[EDIT] It looks like it has something to do with Thread.Sleep, because I've changed 1000ms to 10ms and now it seems to execute cap.Close() everytime. But I still have no idea why it works like that and how to make it work correctly, so it will work with any value.

[Edited by - g0nzo on February 16, 2007 9:33:56 AM]
Niksan2
Niksan2
I guess because they run on different threads it can be hit or miss whether the worker thread can close before your application does and the like, a good article on the subject is http://www.codeproject.com/csharp/workerthread.asp which suggest a way to kill a worker thread if it's not completed upon a form close/or worker cancel, hope this helps.
avianRR
avianRR
I happen to know why your having the problem but not how to fix it since I'm not sure how your calling the cancel event. Basically, if you cancel at the end of the 1000ms sleep time the sleep ends before the cancel does an outright KILL of the thread. If it's at the begining of the 1000ms it doesn't resume before the cancle gets tires of waiting and KILLS the thread thinking it's dead. There might be a way to tell it how long to wait but I haven't gotten into multi-threading with .NET yet. (WIN32 or Linux yes, .NET no)
------------------------------Piggies, I need more piggies![pig][pig][pig][pig][pig][pig]------------------------------Do not invoke the wrath of the Irken elite. [flaming]
taumuon
taumuon
Hi g0nzo,

Your application has a race condition on closing. The background worker is a background thread, means that it's terminated when the application closes (foreground theads continue to run until they're done, and can keep the application alive after the user attempts to close it, look at the docs for System.Threading.Thread.IsBackground for an explanation). It's hit and miss whether your thread will have time to close its resources before it gets terminated, and that's why the Thread.Sleep() makes a difference.

I'm assuming that you have to close the CvCapture in the thread - there's no chance you could create it outside and pass it in, to close it outside the thread, or wrap it in a finalizable object? Well, if you need to close it inside the thread there is a solution.

You want to make sure that your thread has had chance to do its cleanup before it finishes. The BackgroundWorker makes some threading issues easier, but if you want to do something more involved, then you need to understand what's going on in the background. You want to replicate the functionality of System.Threading.Thread.Join() - i.e. you want to let the thread complete before continuing in Form.OnClosed(). You could change your code to use a System.Threading.Thread, but if you want to keep your background worker in, you could use the IsBusy property on your BackgroundWorker member:

protected override void OnClosed(EventArgs e)
{
tracker.Cancel = true;
while (worker.IsBusy)
{
Application.DoEvents();
}
base.OnClosed(e);
}

To try and explain this; the DoEvents() method is a bit of a hack (and I was a bit surprised to see that I needed it for the IsBusy method).

If, where you created your BackgroundWorker, you added the following:

worker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("completed");
};

you'd see that this doesn't fire without the DoEvents() call, which is expected. The RunWorkerCompleted call is marshalled back onto the thread that started the thread (usually the UI thread), and if you're stuck in the while loop then the UI thread is blocked and RunWorkerCompleted can't execute. I'm surprised that the IsBusy property seems to work in the same way.

If you didn't want to use the DoEvents call, then an alternative and slightly neater method would be to have a bool in your class that owns the BackgroundWorker:

volatile bool isThreadDone = false;

and set this to true at the end of your DoWork handler.

Then your OnClosed would loop checking this:

while (!isThreadDone)
{
}

Continually polling this value will thrash your CPU, but that's probably not important as you're closing, but you could instead of having the bool isThreadDone, use a ResetEvent, and wait until that's set.

As an aside, instead of using the cancel property, you could use the WorkerSupportsCancellation/CancelAsync/CancellationPending properties/methods on the BackgroundWorker.

Hope this helps!

Gary
C# Physics Engine Tutorials: www.taumuon.co.uk/jabuka/
g0nzo
g0nzo
Quote:

I'm assuming that you have to close the CvCapture in the thread - there's no chance you could create it outside and pass it in, to close it outside the thread, or wrap it in a finalizable object?

I'm trying to create a library that will return position change (dx and dy) using a camera as an input, so passing a camera object (CVCapture) is not really an option.

Right now I'm using SharperCV (image processing) library, which is a C# wrapper over OpenCV library written in C. I have SharperCV source code and it looks like there's a CVCapture destructor defined that calls Close() method, so in theory it should close itself automatically. I'm not sure why it doesn't work.

Quote:

As an aside, instead of using the cancel property, you could use the WorkerSupportsCancellation/CancelAsync/CancellationPending properties/methods on the BackgroundWorker.

Thanks, I didn't noticed that.

The while loop with Application.DoEvents() seems to do the trick, however I'm not using CVCapture in the loop right now, so I'll have to check if it works, when I'll actually put some code in the Tracker.Run loop :)

Thank you very much!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.