在FormClosing中处理MainForm时从Invoke崩溃

时间:2014-09-21 21:00:18

标签: c# multithreading winforms dispose

我遇到了与线程相关的问题,清理了非托管资源并关闭了我的应用。

在主UI线程中,我有一个方法可以创建类Worker的新实例。在Worker的构造函数中,我启动一个新线程,该线程有一个while(Scanning)循环,它使用Invoke()连续更新我的UI中的某些控件(直到Scanning bool设置为false)。在UI线程中,每当应用程序关闭时(通过X按钮或Application.Exit()等),我都会引发事件FormClosing()。在FormClosing()中,我将Scanning设置为false并对非托管资源进行一些清理(只能在工作线程完成后才能完成,因为它使用了这些资源。问题是当我关闭应用程序时,MainForm显然会立即获得处置,所以应用程序崩溃在Invoke(因为它试图从UI线程运行委托,但该线程被处置)。

为了在UI关闭之前让工作人员完成,我尝试在worker类中创建一个方法StopWorker(),我将Scanning = false,然后是Thread.Join。正如您可以想象的那样,Join导致了一个死锁,因为它使UI线程处于休眠状态,但是Invoke需要UI线程继续前进。

总之,我需要在FormClosing中清理非托管资源。我需要在我这样做之前完成工作线程,因为它使用这些资源。如果处理MainForm,工作线程无法完成(它使用Invoke),因此会产生棘手的情况。

3 个答案:

答案 0 :(得分:1)

根据Hans Passant的回答here,我创建了以下解决方案。它看起来效果很好。

在UI类/线程中:

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();
    }
}

我的工作线程在另一个具有名为Scanning的公共bool属性的类中。它也有这个while循环(注意底部的行):

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()));
}

最后,回到UI类/线程中我有这个方法:

public void StopScanning()
{
    if (!ClosePending) return;
    ApplicationLogger.Get.Log("Worker thread is closing the application...");
    Close();
}

答案 1 :(得分:-1)

你能不能更好地使用BackgroundWorker类/控件?它更容易使用,因为它已经有很多同步内容。

但是如果你有一个单独的线程,在FormClosing事件中,使用:

yourThread.Abort(); 
yourThread.Join(); // or yourThread.Join(1000);  where 1000 is some kind of time out value
你的线程中的

使用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.
}

请注意,Join命令将阻止进一步执行,直到线程停止。因此,我建议超时参数的值不要太高,否则会阻止用户界面并激怒用户。

答案 2 :(得分:-1)

免责声明:我不主张在.NET 4.5+时代使用ThreadManualResetEvent以及最重要的volatile,但自.NET版本以来未指定我已尽力解决问题,同时尽可能保持向后兼容。

这是一个解决方案,它使用轮询变量和ManualResetEvent来阻止FormClosing处理程序的执行,直到循环完成 - 没有任何死锁。在您的方案中,如果您对运行循环的Thread有类级别引用,则可以在Thread.Join处理程序中使用ManualResetEvent.WaitOne而不是FormClosing - 语义将是一样的。

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.");
        }
    }
}

现在,虽然这个解决方案(某种程度上)是针对问题的描述(我尽我所能地解释)而定制的,但我必须做出大量的假设。如果你想要一个更好的答案,你需要发布一个简洁的问题重复 - 不一定是你的生产代码,但至少是一个精简的工作版本它仍然具有所有主要的螺母和螺栓,并展示了您所描述的问题。