对话框.用主UI线程的句柄从后台工作线程调用

本文关键字:线程 后台 工作 调用 用主 UI 对话框 句柄 | 更新日期: 2023-09-27 18:16:31

我有这样的代码:

public void Blah(IWin32Window _this)
{
    for (int i = 0; i < item_quantity; i++)
    {
        try { File.Delete(item[0, i]); }
        catch (Exception ex)
        {
            if (MessageBox.Show(_this, String.Format("Error while accessing {0}'n{1}"
                , item[0, i], ex.Message), "Error", MessageBoxButtons.RetryCancel
                , MessageBoxIcon.Error) == DialogResult.Retry)
            { i--; }
        }
    }
}

…下面的代码放在主UI线程中:

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    AnotherClass.Blah(this);
}

当我执行这段代码时,我得到了不安全的跨线程异常。做这个手术的安全方法是什么?

对话框.用主UI线程的句柄从后台工作线程调用

做这个操作的安全方法是什么?

没有真正安全的方法来做到这一点。消息框不知从哪里跳出来,与用户给出的命令没有任何直接连接。一种失败模式是用户继续使用您的UI,单击鼠标或按空格键。你的消息框会在他点击鼠标或按键前一毫秒弹出。他不会看到留言的。

所以应该做的事情没有完成,用户完全没有意识到。这不是一件好事。你需要调整你的UI,这样这种情况就不会发生。显然,这将要求您以与使用临时消息框不同的方式进行错误报告。当然,许多可能的替代方案可以像报告状态的Label一样简单。StatusStrip可以很好地解决这个问题。

实际的异常是伪造的。它是由内置的诊断程序触发的,该程序检查代码是否以线程安全的方式使用UI。底层的winapi调用是GetParent(),它是极少数可以安全地从工作线程调用和使用的user32 Windows函数之一。我知道的唯一合理的原因是使用控制。CheckForIllegalCrossThreadCalls来解决问题是可以的。但是要解决真正的问题。

我不是在宽恕这种设计,但是您可以将表单传递给Blah(),然后针对引用的表单调用Invoke():

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e)
    {
        if (!backgroundWorker.IsBusy)
        {
            button1.Enabled = false;
            backgroundWorker.RunWorkerAsync();
        }
    }
    private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        SomeClass AnotherClass = new SomeClass();
        AnotherClass.Blah(this);
    }
    private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        button1.Enabled = true;
        MessageBox.Show("Done!");
    }
}
public class SomeClass
{
    public void Blah(Form frm)
    {
        int item_quantity = 5;
        for (int i = 0; i < item_quantity; i++)
        {
            try
            {
                //File.Delete(item[0, i]);
                Console.WriteLine("i = " + i.ToString());
                throw new Exception("duh");
            }
            catch (Exception ex)
            {
                frm.Invoke(new Action(() =>
                    {
                        DialogResult result = MessageBox.Show(frm, String.Format("Error while accessing {0}'n{1}", "something", ex.Message), "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
                        if (result == DialogResult.Retry)
                        {
                            i--;
                        }
                    }));
            }
        }
    }
}

您正在尝试在后台线程上做UI工作,因此跨线程异常。RunWorkerCompletedEventArgs有一个名为Error的属性,它将保存由RunWorkerAsync委托抛出的任何异常。在BackgroundWorker上为RunWorkerCompleted设置处理程序,并检查Error属性是否有值。如果是这样,则在处理程序中提示MessageBox,因为此时您将在UI线程上。在DialogResult.Retry场景中再次调用BackgroundWorkerRunWorkerAsync方法。

(您可能需要调整BackgroundWorkerAnotherClass.Blah以接受i的值,从而为第二次调用BackgroundWorker准备循环条件。DoWorkEventArgs有一个名为Argument的属性,您可以使用它来传递该值。

当从另一个线程调用它时,您需要执行这样的UI代码:

// must use invoke because the timer event is running on a separate thread
this.Invoke(new Action(() =>
{
     MessageBox.Show("Message");
}));