对话框.用主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,这样这种情况就不会发生。显然,这将要求您以与使用临时消息框不同的方式进行错误报告。当然,许多可能的替代方案可以像报告状态的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
场景中再次调用BackgroundWorker
的RunWorkerAsync
方法。
(您可能需要调整BackgroundWorker
和AnotherClass.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");
}));