我正在开发一个启动多个后台工作程序的WinForms应用程序。当一个后台工作程序完成时,如果结果失败,它将通过ShowDialog(this)
方法显示一个对话框。问题是当多个后台工作程序结果失败时,它将同时显示多个对话框。我不认为这是可能的,但显然是。我正在阅读有关消息循环的一些内容,似乎即使对话框打开,消息循环仍在处理消息,这意味着即使对话框已经打开,也会调用runworkercompleted。我虽然可以在对话框中使用“lock(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;
}
这会工作吗?这会导致其他坏事发生吗? (这会阻止消息循环吗?)
答案 0 :(得分:1)
您必须从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");
}
答案 1 :(得分:0)
ShowDialog
没有阻止任何内容(因此您的所有事件都会继续触发),只有该方法中显示模式形式的代码才会等待才能关闭。
您对变量的想法几乎好。如果您只想为所有工人显示一个对话框,那么(不是那么完美解决方案)
public bool dialogOpen = false;
public void x_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// check
if(dialogOpen)
return;
dialogOpen = true;
(new MyDialog("Something went wrong.")).ShowDialog(this);
dialogOpen = false;
}
这里的问题是竞争条件,如果几个工作人员在尚未设置为真时检查dialogOpen
,则会发生这种情况。如果您希望它完美,请改用ManualResetEvent
。
但是,您似乎想要所有工作人员显示错误,但一次只有一个错误。这更难,您的解决方案是错误的,因为您正在阻止UI线程本身。最简单的方法是阻止(阻止)工作人员自己完成,如果其中一个使用对话框(与之前相同,则使用ManualResetEvent
)。
如果您对代码有困难,我明天会帮助您。
答案 2 :(得分:0)
我最后用消息队列进行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;
}
}