是否有 MessageBox.Show 的非阻塞版本(或类似的版本)?
Posted
技术标签:
【中文标题】是否有 MessageBox.Show 的非阻塞版本(或类似的版本)?【英文标题】:Is there a non-blocking version of MessageBox.Show (or something like it)? 【发布时间】:2011-02-19 12:25:23 【问题描述】:长时间延迟更新
我接受 MUG4N 对这个问题的回答,我也想回应一些针对它提出的批评。
ChrisF 说:
...您不能直接从后台线程进行 UI 调用。
这是一个笼统的陈述,并非 100% 正确。让我指出几个事实:
如果您设置Control.CheckForIllegalCrossThreadCalls = false
,您实际上可以随心所欲地进行 UI 调用。 “Ack!” 我听到你说。 “不要永远那样做!”是的,是的——但是为什么?答案:因为有时这会破坏内存。
System.Windows.Forms
中的控制类不是线程安全的,因此有时从后台线程更新它们会损坏内存。但如果这只是有时发生而不是总是,这告诉我这不是 UI 代码的调用本身 ,而是可能导致异常的 UI 代码的潜在不安全冲突。
为了强化第 1 点,请考虑这一点:从后台线程调用 UI 代码的“安全”方式是使用Control.Invoke
或Control.BeginInvoke
,对吗? 但这是 UI 调用;如果我们从非 GUI 线程更新 GUI,这只是我们应该进行的 UI 调用。我的意思是,很明显,它不仅仅是从外部线程调用 Control
对象上的“任何”方法会导致混乱(如果是这种情况,那么我们甚至不能调用 Invoke
并且我们会完全卡住)。同样,无法安全地同时发生的单独 UI 调用的潜在冲突将被证明具有破坏性。
牢记以上两点,问问自己:为什么从非 GUI 线程调用 MessageBox.Show
会不安全?创建并显示一个完全独立的Form
;它的属性不以任何方式与任何其他现有的 GUI 对象交互;事实上,它不能在任何地方以任何方式访问,除了一个:从调用线程访问它的DialogResult
属性(并且只能通过@987654332 @方法的返回值)。
继续前进。康拉德·阿尔布雷希特说:
...鉴于 Show() 在 Dan 的 ref'd 主题中设置了自己的消息泵的断言(这没有得到证实,但我无法反驳)...
这是一个完全公平的观点(尽管我个人对 Jared Par 有足够的尊重,我通常不会倾向于怀疑他所说的)。无论如何,通过Reflector 窥视MessageBox.Show
方法会发现这个sn-p:
Application.BeginModalMessageLoop();
try
result = Win32ToDialogResult(SafeNativeMethods.MessageBox(new HandleRef(owner, zero), text, caption, type));
finally
Application.EndModalMessageLoop();
UnsafeNativeMethods.ThemingScope.Deactivate(userCookie);
进一步查看Application.BeginModalMessageLoop
方法会发现:
ThreadContext.FromCurrent().BeginModalMessageLoop(null);
而这个ThreadContext.FromCurrent
,反过来:
// [Reflector shows that currentThreadContext is a ThreadStatic member. -Dan]
if (currentThreadContext == null)
currentThreadContext = new Application.ThreadContext();
return currentThreadContext;
我对这些较低级别的 Windows 构造了解得不够多,无法完全理解这段代码,但在我看来,这证明了 Jared 在我在旧评论中引用的答案中所说的确切内容(对于好奇的读者: Does MessageBox.Show() automatically marshall to the UI Thread?)。
所以,是的。在这一点上,我完全同意 MUG4N。
(如果有人可以令人信服地辩称我在这里仍然是错误的,请说出来。虽然我觉得我已经为我认为 MUG4N 是正确的原因做了一个很好的说明,但我显然不是 100% 确定。)
原问题
通常您只是想通知用户发生了某些事情,但实际上不需要他们的任何输入。在这种常见情况下,我有时会看到这样的代码:
MessageBox.Show("Something has occurred", "Something", MessageBoxButtons.OK);
众所周知,这段代码会导致出现一个只有一个确定按钮的小弹出窗口。现在事情是这样的:这段代码块(UI 线程)。但在我看来,在绝大多数情况下,如果您仅有一个确定按钮,则几乎不需要阻止。 (阻塞的目的通常不是为了接收用户的一些输入吗?如果用户的only选择是“OK”,在这种典型情况下,阻塞不是毫无意义吗?)
显然,我可以编写自己的小表单,它基本上与MessageBox.Show
所做的完全一样,只是它什么都不返回(没有DialogResult
)并且不会阻塞。但我只是想知道这样的事情是否已经存在而我不知道。
【问题讨论】:
消息框的目的是让用户在他/她继续之前做出决定(如果您有取消等)或确认某事(如果它只是 OK)。 - 如果您认为您需要非模态消息框,我认为您应该重新考虑您的 UI 设计。用例是什么? @Patrick:用例通知用户发生了某些事情。假设用户激活了应用程序中的某个进程,然后离开计算机去洗手间。当他们回来时,他们想知道这个过程是否已经完成。对我来说,消息框是向用户发出信号的最合乎逻辑的方式。同时,可能希望此消息框不会阻止应用程序继续运行其他进程。 【参考方案1】:您需要使用多线程来执行此任务,其中一个线程(主线程)将进行处理,而另一个线程将用于显示消息框。
【讨论】:
@MUG4N:这是一种方法。但是MessageBox.Show
仍然会阻塞调用它的线程,对吧?我猜你的意思是我可以编写一个从后台线程调用MessageBox.Show
的新方法,对吗?
@Dan - 你不能直接从后台线程进行 UI 调用。
@ChrisF:根据我的经验,您可以从后台线程调用MessageBox.Show
。我意识到这可能不被鼓励,但我没有找到任何文件表明绝对不应该这样做。在自己测试之后,我实际上赞成这个答案。它起作用了——没有将Control. CheckForIllegalCrossThreadCalls
设置为true
。
@Conrad:认真的吗?您尝试了 MUG4N 的建议吗?它完美地工作。此外,根据我的研究,似乎从后台线程调用MessageBox.Show
实际上完全没问题。例如,请参阅 Jared Par 对此问题的回答:***.com/questions/559252/…
“它有效”当你碰巧测试它并不能证明线程错误的正确性,线程错误会意外失败。但是,鉴于 Show() 在 Dan 的 ref'd 主题中设置了自己的消息泵的断言(这没有得到证实,但我无法反驳),我将收回我的反对票。 [或者,如果 StacO 允许我,我会撤回]【参考方案2】:
这是一个老问题,但无论如何......
1) 导入 IWshShell('Windows 脚本宿主对象模型')
将 wsh 调暗为新的 IWshRuntimeLibrary.WshShell wsh.Popup(String.Concat(Me.GetType.FullName, vbCrLf, _ Application.ExecutablePath), 0.75, "Title", MessageBoxButtons.OKCancel 或 MessageBoxIcon.Question)
詹斯...
【讨论】:
【参考方案3】:将NotifyIcon 添加到您的应用程序并显示balloon tip 怎么样?不利的一面是通知会在短时间内消失,但如果用户不需要采取行动,这可能对他们来说是最好的。
this question有更多建议。
【讨论】:
+1 在我看来,这实际上更多是关于 UI 隐喻的问题,而不是关于特定 widge 类的线程行为的问题。如果您正在寻找带有警报的非模态行为,那么 MessageBox 以外的其他东西似乎更合适。【参考方案4】:我会尝试直接从 Win32 API 调用 MessageBox 函数,例如:
using System.Runtime.InteropServices;
[DllImport("User32.dll")]
public static extern int MessageBox(int h, string m, string c, int type);
尝试使用空句柄和 APPLMODAL 类型。这可能行得通。
【讨论】:
这就像阻塞一样。 “消息框返回一个整数值,指示用户单击了哪个按钮。”如果它在对话框关闭之前返回,显然它不能这样做。以上是关于是否有 MessageBox.Show 的非阻塞版本(或类似的版本)?的主要内容,如果未能解决你的问题,请参考以下文章