0

I have an issue that relates to threading, cleaning up unmanaged resources and shutting down my app.

In the main UI thread I have a method that creates a new instance of class Worker. In Worker's constructor I start a new thread that has a while(Scanning) loop that updates some controls in my UI using Invoke() continuously (until Scanning bool is set to false). In the UI thread I raise the event FormClosing() whenever the application is closing down (through X button or Application.Exit() etc.). In FormClosing() I set Scanning to false and do some cleanup of unmanaged resources (that can only be done after the worker thread is done, because it uses those resources. The problem is that when I close the app down the MainForm apparently gets instantly disposed, so the app crashes at the Invoke (because it is trying to make a delegate run from UI thread, but that thread is disposed).

In an attempt to make the worker finish before the UI closes I tried to create a method StopWorker() in the worker class where I put Scanning = false, and then Thread.Join. As you can imagine the Join caused a deadlock as it makes the UI thread sleep but the Invoke needs the UI thread to move on.

In summary I need to cleanup unmanaged resources in FormClosing. I need the worker thread to be done before I do that though, as it uses these resources. The worker thread cannot finish (it uses Invoke) if the MainForm is disposed, therefore creating a tricky situation.

Servy
  • 202,030
  • 26
  • 332
  • 449
Anders
  • 580
  • 8
  • 17
  • I don't have a setup where I can test this at the moment, and I admit it's a bit yucky but you could try calling `Application.DoEvents` in a loop in `FormClosing` until your worker thread terminates (after you've set `Scanning` to `false`). – 500 - Internal Server Error Sep 21 '14 at 21:53
  • Same kind of answer as [this one](http://stackoverflow.com/a/1732361/17034). – Hans Passant Sep 21 '14 at 22:09
  • @HansPassant Would it be OK to just put this at the bottom of my scanning method, outisde the perpetual while loop: `MainForm.Get.Invoke((Action)(() => MainForm.Get.CloseForm()));`. Then, as you suggested, I would have a check in FormClosing() that, if `Scanning == true`, set it to false and cancel the closing. The CloseForm() method would contain the same cleanup code that FormClosing does, as well as this.Close(). – Anders Sep 22 '14 at 17:05
  • You ought to be able to make that work. It cannot be a "perpetual while loop" of course, it needs to exit when the user attempts to close the window. – Hans Passant Sep 22 '14 at 17:07
  • @HansPassant Yup, by perpetual I meant running as long as Scanning is set to true. Thanks a ton for the help, I will add my solution to the bottom of my post and link to your previous answer. – Anders Sep 22 '14 at 17:29
  • If you've come up with an answer to your question you should post it as an answer to the question, not as an edit to the question. – Servy Sep 22 '14 at 17:55
  • @Servy Sorry, I did it that way because I did not want to take credit when in fact my solution was somewhat based on Hans' previous answer. I have added my solution in a separate answer now, as suggested. Hopefully it will be of help to others in the future. – Anders Sep 22 '14 at 18:10

3 Answers3

1

Based on Hans Passant's answer here, I created the below solution. It seems to be working very well.

In UI class/thread:

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    var button = sender as Button;
    if (button != null && string.Equals(button.Name, @"CloseButton"))
    {
        //FormClosing event raised by a user-created button action
    }
    else
    {
        //FormClosing event raised by program or the X in top right corner
        //Do cleanup work (stop threads and clean up unmanaged resources)
        if (_bw.Scanning)
        {
            _bw.Scanning = false;
            ClosePending = true;
            e.Cancel = true;
            return;
        }

        //Code to clean up unmanaged resources goes here (dummy code below)
        ApplicationLogger.Get.Log("Doing final cleanup work and exiting application...");
        MemoryHandler.Get.Dispose();
        ApplicationLogger.Get.Dispose();
    }
}

My worker thread is in another class that has a public bool property called Scanning. It also has this while loop (notice the line at the bottom):

private void Worker()
{
    while (Scanning)
    {
        Thread.Sleep(50);

        _sendBackValue[0] = "lbOne";
        _sendBackValue[1] = "blaBla";
        _synch.Invoke(_valueDelegate, _sendBackValue);

        _sendBackValue[0] = "lbTwo";
        _sendBackValue[1] = "blaBla";
        _synch.Invoke(_valueDelegate, _sendBackValue);

        _sendBackValue[0] = "lbThree";
        _sendBackValue[1] = "blaBla";
        _synch.Invoke(_valueDelegate, _sendBackValue);
    }

    MainForm.Get.Invoke((Action)(() => MainForm.Get.StopScanning()));
}

Finally, back in the UI class/thread I have this method:

public void StopScanning()
{
    if (!ClosePending) return;
    ApplicationLogger.Get.Log("Worker thread is closing the application...");
    Close();
}
Community
  • 1
  • 1
Anders
  • 580
  • 8
  • 17
  • Prefer a waitevent to wait on instead of `Sleep` and instead of setting `Scanning` to false, set the `event`. – Peter Ritchie Sep 22 '14 at 18:13
  • @PeterRitchie Sorry, I do not completely understand. Just to clarify, I have the Sleep(50) there so the loop does not consume too much processing power (50ms was ok, I don't need loop to go any faster). Thanks in advance for any clarification. – Anders Sep 22 '14 at 18:28
  • Right, but that forces the thread to be "in use" for about 50 ms and you can't interrupt that. You can wait 50ms with an `EventWaitHandle` to get the same effect, but if you call the `EventWaitHandle.Set` method you'll be able to interrupt it. – Peter Ritchie Sep 22 '14 at 18:35
  • @PeterRitchie Oh, I see. It's like a super-optimizing point right (as noone is going to notice a 50ms lag at shutdown)? The trade off is probably that I need a few more lines of code. Thanks! – Anders Sep 22 '14 at 18:40
  • At 50ms, maybe not. If that gets longer, yes. – Peter Ritchie Sep 22 '14 at 18:43
  • BTW, I don't consider it "over-optimized" because it's a better way of doing it. – Peter Ritchie Sep 22 '14 at 18:45
  • @PeterRitchie I looked into this, maan I have a lot to learn on these features hehe. Is the following correct? In worker class I put: `private ManualResetEvent mre;` at top, and in constructor i put `mre = new ManualResetEvent(false);`. Then in the loop in worker class (which is running on the new thread), I have `mre.WaitOne(50)`. Thanks for the help. – Anders Sep 23 '14 at 18:23
  • @PeterRitchie Oh, I see. so mre.WaitOne() is false all along until I do mre.Set, at which point WaitOne is set to true and the loop exits? – Anders Sep 23 '14 at 19:14
  • Exactly, you'd call `mre.Set()` instead of `Scanning = false`. – Peter Ritchie Sep 23 '14 at 19:23
  • @PeterRitchie Sorry Peter, last question: In my main UI thread/class I have an if statement that used to check the value of Scanning bool (as seen above). In order to achieve same functionality I created a public property called MRE in Worker class to return the ManualResetEvent to a caller. Then in MainForm I switched out the `_bw.Scanner` check with `!_bw.MRE.WaitOne()`. Now as you can imagine, this did not just do a check, but it also forced a deadlock. So I edited it to be `!_bw.MRE.WaitOne(1)` as the check instead (this is in FormClosing by the way). Is that the right way to do it? – Anders Sep 23 '14 at 20:00
-1

Could you not better use the BackgroundWorker class/control? It is much easier to use because it has already a lot of synchronization stuff in it.

But if you have a separate thread, in your FormClosing event, use:

yourThread.Abort(); 
yourThread.Join(); // or yourThread.Join(1000);  where 1000 is some kind of time out value

in your thread use try-excpet-finally construct

try
{
     // do your stuff
}
catch (ThreadAbortException)
{
    // do someting when your thread is aborted
}
finally
{
    // do the clean up. Don't let it take too long.
}

Note that the Join command will block further execution until the thread has stopped. Therefore, I would recommend a not too high value for the time out parameter, otherwise the user interface will be blocked and will irritate users.

Sjips
  • 1,248
  • 2
  • 11
  • 22
  • 2
    Thread.Abort is bad practice, avoid using it! http://stackoverflow.com/questions/710070/timeout-pattern-how-bad-is-thread-abort-really – Mo Patel Sep 22 '14 at 08:34
  • Like Patel suggested, it is best to gracefully exit the worker thread, which is done by setting the loop bool to false and then, when the thread is terminated, proceed with disposing unmanaged resources and shutting down my app. BackgroundWorker probably doesn't add anything I cannot do manually in this case. I appreciate the suggestions though. – Anders Sep 22 '14 at 16:06
-1

Disclaimer: I do not advocate the use of Thread, ManualResetEvent and, above all, volatile in the .NET 4.5+ era, but since the .NET version was not specified I've done my best to address the problem while keeping things as backwards-compatible as possible.

Here's a solution which uses a polling variable and a ManualResetEvent to block the execution of the FormClosing handler until the loop has completed - without any deadlocks. In your scenario if you have a class-level reference to the Thread which runs the loop, you can use Thread.Join instead of ManualResetEvent.WaitOne in the FormClosing handler - the semantics will be the same.

using System;
using System.Threading;
using System.Windows.Forms;

namespace FormClosingExample
{
    public partial class Form1 : Form
    {
        private volatile bool Scanning = true;
        private readonly ManualResetEvent LoopFinishedMre = new ManualResetEvent(false);
        private readonly SynchronizationContext UiContext;

        public Form1()
        {
            this.InitializeComponent();

            // Capture UI context.
            this.UiContext = SynchronizationContext.Current;

            // Spin up the worker thread.
            new Thread(this.Loop).Start();
        }

        private void Loop()
        {
            int i = 0;

            while (this.Scanning)
            {
                // Some operation on unmanaged resource.
                i++;

                // Asynchronous UI-bound action (progress reporting).
                // We can't use Send here because it will deadlock if
                // the call to WaitOne sneaks in between the Scanning
                // check and sync context dispatch.
                this.UiContext.Post(_ =>
                {
                    // Note that it is possible that this will
                    // execute *after* Scanning is set to false
                    // (read: when the form has already closed),
                    // in which case the control *might* have
                    // already been disposed.
                    if (this.Scanning)
                    {
                        this.Text = i.ToString();
                    }
                }, null);

                // Artifical delay.
                Thread.Sleep(1000);
            }

            // Tell the FormClosing handler that the
            // loop has finished and it is safe to
            // dispose of the unmanaged resource.
            this.LoopFinishedMre.Set();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            // Tell the worker that it needs
            // to break out of the loop.
            this.Scanning = false;

            // Block UI thread until Loop() has finished.
            this.LoopFinishedMre.WaitOne();

            // The loop has finished. It is safe to do cleanup.
            MessageBox.Show("It is now safe to dispose of the unmanaged resource.");
        }
    }
}

Now, while this solution is (somewhat) tailored to your description of the problem (which I interpreted to the best of my ability), I had to make a large number of assumptions. If you want a better answer, you'll need to post a concise repro of the problem - not necessarily your production code, but at least a trimmed down working version which still has all the main nuts and bolts in place and exhibits the problem you've described.

Kirill Shlenskiy
  • 9,367
  • 27
  • 39
  • Kirill, thanks a lot for the suggestions. Could I bother you to elaborate on what you mean by not using Thread or volatile in .NET 4.5+? I am using Thread.Sleep, Thread.Join and volatile, quite extensively in my app. I am now going to change my class to a backgroundworker and try both your solution and Hans', to maximize my learning. – Anders Sep 22 '14 at 16:00
  • By blocking the UI thread you introduce the possibility of a deadlock because there is only one thread and if more messages are in the queue for the form that need to be processed in order the set your event, you're dead. With the loop within the form class, this makes it *very* likely someone will eventually update the code and made that happen. – Peter Ritchie Sep 22 '14 at 18:10
  • @Anders, sure. .NET 4 and 4.5 introduce a lot of abstractions (`Task` vs `Thread`), thread synchronisation constructs (`TaskCompletionSource` vs `volatile`, `System.Collections.Concurrent` namespace) and compiler support (`async/await`) that make writing high-quality asynchronous code a lot easier. All of that does come with challenges of its own and requires a different mindset - once you go async, it's async all the way (i.e. no blocking as in my example) - however the final product tends to be simple(r) and not as brittle. – Kirill Shlenskiy Sep 22 '14 at 23:53
  • @PeterRitchie, you are right which is why I was careful *not to* dispatch any messages before setting the event (and yes, some care is required). As it stands the delegate posted to the sync context cannot begin executing until the FormClosing handler has completed, so it is possible for things to happen out of order (which is expected and handled), but not possible for a deadlock to occur, because there is no actual contention for the UI thread. RE breaking in the future - I agree, it's not unlikely; in terms of it breaking now - I cannot fault it logically or through testing. – Kirill Shlenskiy Sep 22 '14 at 23:56