C# 如何在表格';什么是闭幕式?
我有一个生成BackgroundWorker的表单,它应该更新表单自己的文本框(在主线程上),因此C# 如何在表格';什么是闭幕式?,c#,winforms,multithreading,backgroundworker,C#,Winforms,Multithreading,Backgroundworker,我有一个生成BackgroundWorker的表单,它应该更新表单自己的文本框(在主线程上),因此Invoke((Action)()呼叫。 如果在HandleClosingEvent中,我只是执行bgWorker.CancelAsync()然后在Invoke(…)调用中获得ObjectDisposedException,这是可以理解的。但如果我坐在HandleClosingEvent中等待bgWorker完成,那么.Invoke(…)永远不会返回,这也是可以理解的 你知道如何关闭此应用程序而不出
Invoke((Action)()代码>呼叫。
如果在HandleClosingEvent
中,我只是执行bgWorker.CancelAsync()
然后在Invoke(…)
调用中获得ObjectDisposedException
,这是可以理解的。但如果我坐在HandleClosingEvent
中等待bgWorker完成,那么.Invoke(…)永远不会返回,这也是可以理解的
你知道如何关闭此应用程序而不出现异常或死锁吗
以下是simple Form1类的3种相关方法:
public Form1() {
InitializeComponent();
Closing += HandleClosingEvent;
this.bgWorker.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
while (!this.bgWorker.CancellationPending) {
Invoke((Action) (() => { this.textBox1.Text = Environment.TickCount.ToString(); }));
}
}
private void HandleClosingEvent(object sender, CancelEventArgs e) {
this.bgWorker.CancelAsync();
/////// while (this.bgWorker.CancellationPending) {} // deadlock
}
一个可行但太复杂的解决方案。这样做的目的是产生一个计时器,它将继续尝试关闭窗体,而窗体将拒绝关闭,直到所说的bgWorker
死亡
private void HandleClosingEvent(object sender, CancelEventArgs e) {
if (!this.bgWorker.IsBusy) {
// bgWorker is dead, let Closing event proceed.
e.Cancel = false;
return;
}
if (!this.bgWorker.CancellationPending) {
// it is first call to Closing, cancel the bgWorker.
this.bgWorker.CancelAsync();
this.timer1.Enabled = true;
}
// either this is first attempt to close the form, or bgWorker isn't dead.
e.Cancel = true;
}
private void timer1_Tick(object sender, EventArgs e) {
Trace.WriteLine("Trying to close...");
Close();
}
我会将与文本框关联的SynchronizationContext传递给BackgroundWorker,并使用它在UI线程上执行更新。使用SynchronizationContext.Post,您可以检查控件是否已释放或正在释放。您不能等待表单析构函数中的信号吗
AutoResetEvent workerDone = new AutoResetEvent();
private void HandleClosingEvent(object sender, CancelEventArgs e)
{
this.bgWorker.CancelAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (!this.bgWorker.CancellationPending) {
Invoke((Action) (() => { this.textBox1.Text =
Environment.TickCount.ToString(); }));
}
}
private ~Form1()
{
workerDone.WaitOne();
}
void backgroundWorker1_RunWorkerCompleted( Object sender, RunWorkerCompletedEventArgs e )
{
workerDone.Set();
}
我所知道的唯一一种死锁安全和异常安全的方法是实际取消FormClosing事件。如果BGW仍在运行,则设置e.Cancel=true,并设置标志以指示用户请求关闭。然后检查BGW的RunWorkerCompleted事件处理程序中的该标志,如果设置了,则调用Close()
private bool closePending;
protected override void OnFormClosing(FormClosingEventArgs e) {
if (backgroundWorker1.IsBusy) {
closePending = true;
backgroundWorker1.CancelAsync();
e.Cancel = true;
this.Enabled = false; // or this.Hide()
return;
}
base.OnFormClosing(e);
}
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (closePending) this.Close();
closePending = false;
// etc...
}
首先,ObjectDisposedException只是一个可能的陷阱。在大量情况下,运行OP代码会产生以下InvalidOperationException:
无法调用Invoke或BeginInvoke
在控件上,直到窗口句柄
已创建
我认为这可以通过在“已加载”回调上启动worker而不是构造函数来进行修改,但是如果使用BackgroundWorker的进度报告机制,则可以完全避免这种痛苦。以下措施效果良好:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (!this.bgWorker.CancellationPending)
{
this.bgWorker.ReportProgress(Environment.TickCount);
Thread.Sleep(1);
}
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.textBox1.Text = e.ProgressPercentage.ToString();
}
我有点劫持了percentage参数,但是一个可以使用另一个重载来传递任何参数
有趣的是,删除上面的睡眠调用会阻塞UI,消耗大量CPU,并不断增加内存使用。我想这与GUI的消息队列过载有关。然而,在睡眠调用保持不变的情况下,CPU的使用率实际上为0,内存的使用率似乎也很好。为谨慎起见,是否应使用高于1ms的值?请在此提供专家意见更新:似乎只要更新不太频繁,就可以:
在任何情况下,我都无法预见GUI的更新间隔必须短于几毫秒的情况(至少在有人观看GUI的情况下),因此我认为大部分时间进度报告都是正确的选择这是我的解决方案(很抱歉,它是在VB.Net中)
当我运行FormClosing事件时,我运行BackgroundWorker1.CancelAsync()将CancellationPending值设置为True。不幸的是,该程序从未真正有机会检查值CancellationPending值以将e.Cancel设置为true(据我所知,这只能在BackgroundWorker1\u DoWork中完成)。
我没有删除那条线,尽管它看起来并没有什么不同
我添加了一行代码,将全局变量bClosingForm设置为True。然后,我在BackgroundWorker_WorkCompleted中添加了一行代码,以在执行任何结束步骤之前检查e.Cancelled和全局变量bClosingForm
使用这个模板,您应该能够随时关闭窗体,即使后台工作人员在某个中间(这可能不好,但它肯定会发生,所以它也可能被处理)。我不确定是否有必要,但在这一切发生后,您可以在Form_Closed事件中完全处理后台工作程序
Private bClosingForm As Boolean = False
Private Sub SomeFormName_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
bClosingForm = True
BackgroundWorker1.CancelAsync()
End Sub
Private Sub backgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'Run background tasks:
If BackgroundWorker1.CancellationPending Then
e.Cancel = True
Else
'Background work here
End If
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If Not bClosingForm Then
If Not e.Cancelled Then
'Completion Work here
End If
End If
End Sub
我找到了另一种方法。如果你有更多的背景员工,你可以:
List<Thread> bgWorkersThreads = new List<Thread>();
您可以使用的程序:
foreach (Thread thread in this.bgWorkersThreads)
{
thread.Abort();
}
我在Word外接程序控件中使用了它,我在CustomTaskPane
中使用了它。如果有人提前关闭文档或应用程序,而我的所有后台工作人员都完成了他们的工作,则会引发一些COM异常
(我不记得是哪个异常)。CancelAsync()
不起作用
但是有了它,我可以在关闭事件之前立即关闭文档中后台工作人员使用的所有线程,我的问题就解决了。另一种方法:
if (backgroundWorker.IsBusy)
{
backgroundWorker.CancelAsync();
while (backgroundWorker.IsBusy)
{
Application.DoEvents();
}
}
那我呢,我是山德勒创造的
Private Sub BwDownload_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BwDownload.RunWorkerCompleted
If Me.IsHandleCreated Then
'Form is still open, so proceed
End If
End Sub
您的backgroundworker不应使用Invoke来更新文本框。它应该很好地要求UI线程使用EventProgressChanged更新文本框,并将值放入附加的文本框中
在事件关闭(或者可能是事件关闭)期间,UI线程会记住表单在取消backgroundworker之前已关闭
收到progressChanged后,UI线程检查表单是否已关闭,只有在未关闭时,才会更新文本框。这不会对所有人都有效,但如果您定期在BackgroundWorker中执行某项操作,例如每秒钟或每10秒钟(可能轮询服务器)这似乎可以很好地以有序的方式停止流程,并且没有错误消息(至少到目前为止),并且易于遵循
public void StopPoll()
{
MyBackgroundWorker.CancelAsync(); //Cancel background worker
AutoResetEvent1.Set(); //Release delay so cancellation occurs soon
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
while (!MyBackgroundWorker.CancellationPending)
{
//Do some background stuff
MyBackgroundWorker.ReportProgress(0, (object)SomeData);
AutoResetEvent1.WaitOne(10000);
}
}
我真的不明白,在这种情况下,如果您使用此选项,为什么DoEvents会被视为如此糟糕的选择。enabled=false。我想这会使它很整洁
protected override void OnFormClosing(FormClosingEventArgs e) {
this.Enabled = false; // or this.Hide()
e.Cancel = true;
backgroundWorker1.CancelAsync();
while (backgroundWorker1.IsBusy) {
Application.DoEvents();
}
e.cancel = false;
base.OnFormClosing(e);
}
windowsformsssynchronizationcontext.Post(…)
只调用BeginInvoke(…)
,因此它与我正在做的Invoke()没有太大区别。除非我遗漏了什么,您能详细说明一下吗?您是否尝试使用BegingVoke而不是Invoke,这样您就不必等到invokemessage返回?是的
protected override void OnFormClosing(FormClosingEventArgs e) {
this.Enabled = false; // or this.Hide()
e.Cancel = true;
backgroundWorker1.CancelAsync();
while (backgroundWorker1.IsBusy) {
Application.DoEvents();
}
e.cancel = false;
base.OnFormClosing(e);
}