简体   繁体   English

ShowDialog在BackgroundWorker中RunWorkerCompleted

[英]ShowDialog in BackgroundWorker RunWorkerCompleted

I'm working on a WinForms application that is launching several background workers. 我正在启动一个WinForms应用程序,该应用程序正在启动多个后台工作程序。 When one background worker is complete, if the result is a failure, it will show a dialog box via the ShowDialog(this) method. 当一个后台工作者完成时,如果结果失败,它将通过ShowDialog(this)方法显示一个对话框。 The problem is when multiple background worker results are fail, it will show multiple dialogs at the same time. 问题是当多个后台工作程序结果失败时,它将同时显示多个对话框。 I didn't think this was possible, but it apparently is. 我不认为这是可能的,但显然是可能的。 I was reading some stuff about the the Message Loop and it seems that even though a dialog is open, the message loop is still processing messages, which means that the runworkercompleted will be called even if a dialog is open already. 我正在阅读有关消息循环的一些内容,看来即使打开了对话框,消息循环仍在处理消息,这意味着即使已经打开对话框,也会调用runworkercompleted。 I though that I could use a "lock (myObject)" on the dialog, but it appears that it does not, I'm guessing since that same thread is calling the lock each time. 我虽然可以在对话框上使用“锁(myObject)”,但是似乎没有,我猜是因为同一线程每次都在调用该锁。

So what is the appropriate way to fix this? 那么解决此问题的合适方法是什么? I'm half tempted to just use a flag and a loop like this: 我很想只使用一个标志和这样的循环:

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

Would this even work? 这甚至行得通吗? Would this cause other bad things to happen? 这会导致其他不好的事情发生吗? (Would this block the message loop?) (这会阻止消息循环吗?)

You'd have to ask the user from inside the BackgroundWorker() worker threads, the DoWork() methods themselves. 您必须从BackgroundWorker()工作线程内部询问用户,DoWork()方法本身。 A simple lock statement will prevent them from attempting to display more than one dialog. 一个简单的lock语句将阻止它们尝试显示多个对话框。 You can use Invoke() to properly display the dialog on the main UI thread. 您可以使用Invoke()在主UI线程上正确显示对话框。

Here's a simplified example: 这是一个简化的示例:

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

ShowDialog is not blocking anything (so all your events will keep firing), only code in that method, where you show modal form, will wait for it to close. ShowDialog不会阻止任何东西(因此所有事件都将继续触发),只有该方法中显示模式形式的代码将等待它关闭。

Your idea with variables is almost good. 您对变量的想法几乎是件好事。 If you want to display just one dialog for all workers, then ( not so perfect solution) 如果您只想为所有工作人员显示一个对话框,那么( 不是那么完美的解决方案)

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

Problem here is race condition, which occurs if several workers will check dialogOpen when it is not yet set to true by either. 这里的问题是竞态条件,如果其中任何一个都没有设置为true时,如果几个工作人员将检查dialogOpen ,则会出现这种情况。 If you want it to be perfect, then use ManualResetEvent instead. 如果您希望它是完美的,请改用ManualResetEvent

However, you seems to want what all workers will display error, but only one error at time. 但是,您似乎想让所有工作人员都显示错误,但是一次只显示一个错误。 This is harder, your solution is wrong, because you are blocking UI thread itself. 这很难,您的解决方案是错误的,因为您要阻止UI线程本身。 The easiest would be to prevent (block) workers themselves from completing, if one of them is using dialog (same as before, use ManualResetEvent ). 最简单的方法是防止工人自己完成(阻止)工人,如果其中一个工人正在使用对话框(与以前一样,请使用ManualResetEvent )。

If you have difficulties with the code, I'll help you tomorrow. 如果您在使用代码方面遇到困难,我明天会为您提供帮助。

I ended up doing the bool check with a message queue. 我最终通过消息队列进行布尔检查。 This appears to be working. 这似乎正在工作。 If there is a race condition like Sinatr was suggesting, then I'm not sure why, as from my understanding, it is all processing on the UI thread. 如果像Sinatr所建议的那样存在竞争状况,那么根据我的理解,我不确定为什么全部在UI线程上进行处理。

Here is a chopped version of what I did to get it working. 这是我为使其正常工作所做的精简版。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM