我遇到了与线程相关的问题,清理了非托管资源并关闭了我的应用。
在主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),因此会产生棘手的情况。
答案 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+时代使用Thread
,ManualResetEvent
以及最重要的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.");
}
}
}
现在,虽然这个解决方案(某种程度上)是针对问题的描述(我尽我所能地解释)而定制的,但我必须做出大量的假设。如果你想要一个更好的答案,你需要发布一个简洁的问题重复 - 不一定是你的生产代码,但至少是一个精简的工作版本它仍然具有所有主要的螺母和螺栓,并展示了您所描述的问题。