0

我有这个代码:

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

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

4

4 回答 4

2

执行此操作的安全方法是什么?

没有真正安全的方法可以做到这一点。消息框突然弹出,与用户给出的命令没有任何直接联系。一种失败模式是用户继续使用您的 UI,单击鼠标或按下空格键。在他单击鼠标或按下键之前,您的消息框会弹出一毫秒。他永远不会看到消息。

所以应该做一些事情,它没有完成,用户完全没有意识到它。不是什么好事。您需要修改您的 UI,这样这种情况就不会发生。显然,这将要求您以不同于使用临时消息框的方式报告错误。当然,许多可能的替代方案可以像报告状态的标签一样简单。StatusStrip 对此很有用。

实际的例外是虚假的。它由内置诊断程序触发,该诊断程序检查代码是否以线程安全的方式使用 UI。底层的 winapi 调用是 GetParent(),它是极少数可以从工作线程安全调用和使用的 user32 Windows 函数之一。我知道在哪里使用 Control.CheckForIllegalCrossThreadCalls 来解决问题的唯一正当理由是可以的。但要解决真正的问题。

于 2013-06-03T15:37:03.050 回答
1

我不是在宽恕这种设计,但是您可以将 Form 传递给 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--;
                        }
                    }));
            }
        }
    }
}
于 2013-06-03T16:59:37.430 回答
1

您正在尝试在后台线程上进行 UI 工作,因此会出现跨线程异常。RunWorkerCompletedEventArgs有一个名为的属性,它将保存委托Error抛出的任何异常。为您RunWorkerAsync设置一个处理程序并检查该属性是否具有值。如果是,请在处理程序中提示,因为此时您将在 UI 线程上。在场景中再次调用'方法。RunWorkerCompletedBackgroundWorkerErrorMessageBoxBackgroundWorkerRunWorkerAsyncDialogResult.Retry

(您可能需要调整您的BackgroundWorkerandAnotherClass.Blah以获取i第二次调用您的循环条件的值BackgroundWorker。 TheDoWorkEventArgs有一个名为的属性Argument,您可以使用它来传递该值。)

于 2013-06-03T15:14:27.943 回答
0

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

// must use invoke because the timer event is running on a separate thread
this.Invoke(new Action(() =>
{
     MessageBox.Show("Message");
}));
于 2013-06-03T15:16:25.963 回答