BackgroundWorker RunWorkerCompleted中的ShowDialog

时间:2014-02-20 15:41:57

标签: c# winforms backgroundworker

我正在开发一个启动多个后台工作程序的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;
}

这会工作吗?这会导致其他坏事发生吗? (这会阻止消息循环吗?)

3 个答案:

答案 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;
    }
}