C# BackgroundWorker Runworker Completed中的ShowDialog
我正在开发一个WinForms应用程序,它将启动几个后台工作程序。当一个后台工作程序完成时,如果结果是失败,它将通过C# BackgroundWorker Runworker Completed中的ShowDialog,c#,winforms,backgroundworker,C#,Winforms,Backgroundworker,我正在开发一个WinForms应用程序,它将启动几个后台工作程序。当一个后台工作程序完成时,如果结果是失败,它将通过ShowDialog(this)方法显示一个对话框。问题是当多个后台工作程序结果失败时,它将同时显示多个对话框。我不认为这是可能的,但显然是。我读了一些关于消息循环的内容,似乎即使对话框打开,消息循环仍在处理消息,这意味着即使对话框已经打开,也会调用runworkercompleted。我原以为我可以在对话框上使用“锁(myObject)”,但现在看来不行,我猜是因为同一个线程每次
ShowDialog(this)
方法显示一个对话框。问题是当多个后台工作程序结果失败时,它将同时显示多个对话框。我不认为这是可能的,但显然是。我读了一些关于消息循环的内容,似乎即使对话框打开,消息循环仍在处理消息,这意味着即使对话框已经打开,也会调用runworkercompleted。我原以为我可以在对话框上使用“锁(myObject)”,但现在看来不行,我猜是因为同一个线程每次都在调用锁
那么,解决这个问题的合适方法是什么?我有点想用这样的标志和循环:
public bool dialogOpen = false;
public bool cancelMessages = false;
public void x_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
while (dialogOpen) {}
if (cancelMessages) return;
dialogOpen = true;
MyDialog dlg = new MyDialog("Something went wrong.");
if (dlg.ShowDialog(this) == DialogResult.Cancel) cancelMessages = true;
dialogOpen = false;
}
这能奏效吗?这会导致其他不好的事情发生吗?(这会阻止消息循环吗?
ShowDialog
不会阻止任何内容(因此所有事件都将持续触发),只有该方法中显示模态形式的代码才会等待它关闭
你关于变量的想法几乎是好的。如果您只想为所有工作人员显示一个对话框,那么(不是那么完美的解决方案)
这里的问题是竞态条件,当两个工人都未将其设置为true时,如果有几个工人将检查dialogOpen
,就会出现竞态条件。如果您希望它是完美的,则使用ManualResetEvent
但是,您似乎希望所有工作人员都显示错误,但一次只显示一个错误。更难的是,您的解决方案是错误的,因为您正在阻止UI线程本身。最简单的方法是防止(阻止)工作人员自己完成,如果其中一人正在使用dialog(与以前一样,使用ManualResetEvent
)
如果您在代码方面有困难,我明天会帮助您。您必须从BackgroundWorker()工作线程内部,即DoWork()方法本身,询问用户。一个简单的
lock
语句将阻止他们尝试显示多个对话框。可以使用Invoke()在主UI线程上正确显示对话框
下面是一个简化的示例:
private void button1_Click(object sender, EventArgs e)
{
for(int i = 1; i <=5; i++)
{
BackgroundWorker x = new BackgroundWorker();
x.DoWork += x_DoWork;
x.RunWorkerCompleted += x_RunWorkerCompleted;
x.RunWorkerAsync();
}
}
private bool cancelMessages = false;
private Object dialogLock = new object();
void x_DoWork(object sender, DoWorkEventArgs e)
{
// ... some work ...
System.Threading.Thread.Sleep(5000); // five seconds worth of "work"
if (true) // some error occurred
{
lock (dialogLock) // only one worker thread can enter here at a time
{
if (!cancelMessages) // if error messages haven't been turned off, ask the user
{
// ask the user on the main UI thread:
// *Invoke() is SYNCHRONOUS, so code won't go continue until after "dlg" is dismissed
this.Invoke((MethodInvoker)delegate() {
MyDialog dlg = new MyDialog("Something went wrong.");
if (dlg.ShowDialog(this) == DialogResult.Cancel)
cancelMessages = true;
});
}
}
}
}
public void x_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("RunWorkerCompleted");
}
private void按钮1\u单击(对象发送者,事件参数e)
{
对于(int i=1;i我最终使用一个消息队列执行bool检查。这似乎是可行的。如果存在Sinatr建议的竞争条件,那么我不确定为什么,据我所知,这都是在UI线程上处理的
这是我为了让它工作所做的一个切碎的版本
public List<BGWOProcess> messageQueue = new List<BGWOProcess>();
public static bool DialogOpen = false;
public static bool CancelPending = false;
public void loginProcess_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BGWOProcess result = (BGWOProcess)e.Result;
if (!result.Result)
{
if (CancelPending) return;
if (!DialogOpen) DialogOpen = true;
else
{
messageQueue.Add(result);
return;
}
try
{
processFailedMessage(result);
}
catch (Exception) { }
DialogOpen = false;
}
else
{
//...
}
}
public void processFailedMessage(BGWOProcess result)
{
MyMessage msg = new MyMessage("The process " + result.Label + " failed: " + result.Error + " Retry?");
if (msg.ShowDialog(this) == System.Windows.Forms.DialogResult.Yes)
{
// Retry request.
Queue(result.func, result.Label, result.progressIncrement);
if (messageQueue.Count > 0)
{
BGWOProcess nextMessage = messageQueue[0];
messageQueue.Remove(nextMessage);
processFailedMessage(nextMessage);
}
}
else
{
r = false;
CancelPending = true;
// Fail.
DialogResult = System.Windows.Forms.DialogResult.Abort;
}
}
public List messageQueue=new List();
公共静态bool DialogOpen=false;
公共静态bool CancelPending=false;
public void loginProcess\u RunWorkerCompleted(对象发送方,runworkercompletedeventarge)
{
BGWOProcess result=(BGWOProcess)e.result;
如果(!result.result)
{
如果(取消挂起)返回;
如果(!DialogOpen)DialogOpen=true;
其他的
{
messageQueue.Add(结果);
返回;
}
尝试
{
processFailedMessage(结果);
}
捕获(异常){}
DialogOpen=false;
}
其他的
{
//...
}
}
public void processFailedMessage(BGWOProcess结果)
{
MyMessage msg=newmymessage(“进程”+result.Label+”失败:“+result.Error+”重试?”);
if(msg.ShowDialog(this)=System.Windows.Forms.DialogResult.Yes)
{
//重试请求。
队列(result.func、result.Label、result.progressIncrement);
如果(messageQueue.Count>0)
{
BGWOProcess nextMessage=messageQueue[0];
messageQueue.Remove(nextMessage);
processFailedMessage(下一条消息);
}
}
其他的
{
r=假;
CancelPending=true;
//失败。
DialogResult=System.Windows.Forms.DialogResult.Abort;
}
}
我刚刚尝试了循环,它阻止了一切。(对话框不起作用。)我可能需要建立一个消息队列。为什么不只跟踪后台线程完成的数量与声明的数量,当计数下降到0时,只有在出现任何失败时才显示消息框?是的,这是很有可能的。消息框不会阻止Windows消息的正常流动。它只会禁用其余的窗口在应用程序中,阻止用户生成输入消息。不使用消息框,而是使用更类似于显示消息列表的窗口可能是合适的。这也可以防止用户在愉快地单击时意外关闭框。我想我不明白这是如何工作的。如果消息s发送到RunWorkerCompleted发生在主UI线程上,那么为什么根据Sinatr的回答会有竞态条件。我已经尝试实现布尔检查。如果所有的都在同一线程上处理,则不应该有竞态条件…消息框不是这里的选项。对话框更多的是“继续/忽略”或“取消/退出”选项。因此,如果用户点击“取消/退出”,我们不关心其余的对话框。我最终选择了另一条路线,但这似乎也会起作用。
public List<BGWOProcess> messageQueue = new List<BGWOProcess>();
public static bool DialogOpen = false;
public static bool CancelPending = false;
public void loginProcess_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BGWOProcess result = (BGWOProcess)e.Result;
if (!result.Result)
{
if (CancelPending) return;
if (!DialogOpen) DialogOpen = true;
else
{
messageQueue.Add(result);
return;
}
try
{
processFailedMessage(result);
}
catch (Exception) { }
DialogOpen = false;
}
else
{
//...
}
}
public void processFailedMessage(BGWOProcess result)
{
MyMessage msg = new MyMessage("The process " + result.Label + " failed: " + result.Error + " Retry?");
if (msg.ShowDialog(this) == System.Windows.Forms.DialogResult.Yes)
{
// Retry request.
Queue(result.func, result.Label, result.progressIncrement);
if (messageQueue.Count > 0)
{
BGWOProcess nextMessage = messageQueue[0];
messageQueue.Remove(nextMessage);
processFailedMessage(nextMessage);
}
}
else
{
r = false;
CancelPending = true;
// Fail.
DialogResult = System.Windows.Forms.DialogResult.Abort;
}
}