【问题标题】:ShowDialog in BackgroundWorker RunWorkerCompletedShowDialog in BackgroundWorker RunWorkerCompleted
【发布时间】:2014-02-20 15:41:57
【问题描述】:

我正在开发一个启动多个后台工作人员的 WinForms 应用程序。当一个后台工作人员完成时,如果结果失败,它将通过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;
}

这还能用吗?这会导致其他不好的事情发生吗? (这会阻塞消息循环吗?)

【问题讨论】:

  • 我刚刚尝试了循环,它阻止了一切。 (对话框不起作用。)我可能必须建立一个消息队列。
  • 为什么不只跟踪已完成的后台线程数量与规定的数量,然后当计数降至 0 时,如果有任何失败,您才会显示消息框
  • 是的,这很有可能。消息框不会阻止 Windows 消息的正常流动。它只是禁用应用程序中的其余窗口,阻止用户生成输入消息。不使用消息框但更类似于显示消息列表的窗口可能是合适的。这也可以防止用户在愉快地点击离开时意外关闭框。
  • 我想我不明白它是如何工作的。如果发送到 RunWorkerCompleted 的消息发生在主 UI 线程上,那么为什么根据 Sinatr 的答案存在竞争条件。我试过实施布尔检查。如果所有内容都在同一个线程上处理,则不应该存在竞争条件...此处不能选择消息框。

标签: c# winforms backgroundworker


【解决方案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");
    }

【讨论】:

  • 我最终选择了不同的路线,但这似乎也可以。
【解决方案2】:

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

这里的问题是竞争条件,如果多个工作人员在尚未将其设置为 true 时检查 dialogOpen,就会发生这种情况。如果您希望它完美,请改用ManualResetEvent

但是,您似乎希望 所有 工作人员会显示错误,但一次只显示一个错误。这更难,您的解决方案是错误的,因为您阻塞了 UI 线程本身。最简单的方法是阻止(阻止)工作人员自己完成,如果其中一个正在使用对话框(和以前一样,使用ManualResetEvent)。

如果您对代码有困难,我明天会帮助您。

【讨论】:

  • 对话框更像是“继续/忽略”或“取消/退出”选项。因此,如果用户点击取消/退出,我们将不关心对话框的其余部分。
【解决方案3】:

我最终使用消息队列进行布尔检查。这似乎工作。如果有像 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;
    }
}

【讨论】:

    猜你喜欢
    • 2011-02-17
    • 1970-01-01
    • 1970-01-01
    • 2011-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多