是否有非阻塞版本的 MessageBox.Show (或类似的版本)?

发布于 2024-09-01 12:36:35 字数 3196 浏览 9 评论 0 原文

迟来的更新

我接受MUG4N对这个问题的回答,并且我也想回应一些针对它的批评。

克里斯 F 说:

...您无法直接从后台线程进行 UI 调用。

这是一个笼统的声明,并非 100% 真实。让我指出一些事实:

  1. 如果您设置 Control.CheckForIllegalCrossThreadCalls = false,您实际上可以随心所欲地进行 UI 调用“啊!”我听到你说。 “永远不要这样做!”是的,是的 - 但为什么?答案:因为有时这会损坏内存。

    System.Windows.Forms 中的控件类未编写为线程安全的,因此有时从后台线程更新它们可能会损坏内存。但如果这种情况只是有时发生而不是总是,那么这告诉我的是它本身并不是UI代码的调用 ,而是可能导致异常的 UI 代码潜在不安全冲突

  2. 为了强调第 1 点,请考虑这一点:从后台线程调用 UI 代码的“安全”方法是使用 Control.InvokeControl.BeginInvoke , 正确的? 但这 UI 调用;如果我们从非 GUI 线程更新 GUI,这只是我们应该进行的 UI 调用。我的意思是,显然,这不仅仅是从外部线程调用 Control 对象上的“任何”方法,这会导致混乱(如果是这种情况,那么我们甚至无法调用调用,我们就会完全陷入困境)。同样,无法安全同时发生的单独 UI 调用之间的潜在冲突将被证明是具有破坏性的。

  3. 牢记以上两点,问问自己:为什么从非 GUI 线程调用 MessageBox.Show 不安全?创建并显示一个完全独立的Form;它的属性不会以任何方式与任何其他现有的 GUI 对象交互;事实上,它不能在任何地方任何方式访问,除了一种:从调用线程访问它的 DialogResult 属性(并且仅通过 Show 方法的返回值)。

继续前进。康拉德·阿尔布雷希特说:

...鉴于 Show() 在 Dan 引用的主题中设置了自己的消息泵的断言(未得到证实,但我无法反驳)...

这是一个完全公平的观点(尽管我个人认为我对 Jared Par 抱有足够高的敬意,因此我通常不会怀疑他所说的话)。无论如何,通过 Reflector< 查看 MessageBox.Show 方法/a> 揭示了这个片段:

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 在我的旧评论中引用的答案中所说的内容(对于好奇的读者:MessageBox.Show() 是否自动编组到 UI 线程?)。

所以,是的。在这一点上我完全同意 MUG4N 的观点。

(如果有人能令人信服地辩称我在这里仍然是错误的,请大声说出来。虽然我觉得我已经提出了一个很好的理由来说明为什么我相信 MUG4N 是正确的,但我显然不是 100% 确定。)


原始问题

通常你只是想通知用户发生了一些事情,但实际上不需要他们进行任何输入。在这种常见场景中,我有时会看到这样的代码:

MessageBox.Show("Something has occurred", "Something", MessageBoxButtons.OK);

众所周知,此代码会导致出现一个小弹出窗口,其中仅包含一个 OK 按钮。现在事情是这样的:这段代码阻塞了(UI 线程)。但在我看来,在绝大多数情况下,如果您只有有一个确定按钮,则几乎不需要阻止。 (阻止的目的通常不是为了接收用户的一些输入吗?如果用户的唯一选择是“确定”,在这种典型情况下,阻止是不是毫无意义?)

显然我可以编写我自己的小表单,它的功能基本上与 MessageBox.Show 的功能完全相同,只是它不返回任何内容(没有 DialogResult)并且不会阻塞。但我只是想知道是否已经存在我不知道的类似事情。

Long-delayed update

I'm accepting MUG4N's answer to this question, and I also want to respond to some of the criticisms that were raised against it.

ChrisF said:

...you can't make UI calls directly from background threads.

This is a blanket statement, and is not 100% true. Let me just point out a few facts:

  1. You can actually make UI calls all you want if you set Control.CheckForIllegalCrossThreadCalls = false. "Ack!" I hear you saying. "Don't ever do that!" Yes, yes -- but why? The answer: because sometimes this will corrupt memory.

    The control classes in System.Windows.Forms are not written to be thread-safe, so sometimes updating them from background threads can corrupt memory. But if this only sometimes happens and not always, what this tells me is that it is not the calling of UI code per se, but rather the potentially unsafe collision of UI code that can cause exceptions.

  2. To reinforce point 1, consider this: the "safe" way to invoke UI code from a background thread is to do so using Control.Invoke or Control.BeginInvoke, right? But this is a UI call; it's just the UI call we're supposed to make if we're updating the GUI from a non-GUI thread. What I mean is, clearly, it is not simply invoking "any" method on a Control object from an outside thread that's going to cause chaos (if that were the case, then we couldn't even call Invoke and we'd be stuck completely). Again, it's the potential collision of separate UI calls that cannot safely occur simultaneously that will prove destructive.

  3. Keeping the above two points in mind, ask yourself: why would it be unsafe to call MessageBox.Show from a non-GUI thread? A completely separate Form is created and displayed; its properties do not in any way interact with any other existing GUI object; in fact, it cannot be accessed anywhere in any manner, except for one: from the calling thread, which accesses its DialogResult property (and only that via the Show method's return value).

Moving along. Conrad Albrecht said:

...given the assertion that Show() sets up its own message pump in Dan's ref'd topic, (which was not substantiated, but which I can't refute)...

This is a totally fair point (though I personally hold Jared Par in high enough esteem that I wouldn't generally be inclined to doubt what he says). In any case, a peek at the MessageBox.Show method through Reflector reveals this snippet:

Application.BeginModalMessageLoop();
try
{
    result = Win32ToDialogResult(SafeNativeMethods.MessageBox(new HandleRef(owner, zero), text, caption, type));
}
finally
{
    Application.EndModalMessageLoop();
    UnsafeNativeMethods.ThemingScope.Deactivate(userCookie);
}

A further peek into the Application.BeginModalMessageLoop method reveals this:

ThreadContext.FromCurrent().BeginModalMessageLoop(null);

And this ThreadContext.FromCurrent, in turn:

// [Reflector shows that currentThreadContext is a ThreadStatic member. -Dan]
if (currentThreadContext == null)
{
    currentThreadContext = new Application.ThreadContext();
}
return currentThreadContext;

I don't know enough about these lower-level Windows constructs to fully understand this code, but this seems to me to be evidence of exactly what Jared was saying in the answer I referenced in my old comment (for curious readers: Does MessageBox.Show() automatically marshall to the UI Thread?).

So, yeah. I am totally in agreement with MUG4N on this one.

(If anyone can convincingly argue that I am still mistaken here, please speak up. Although I feel I've made a pretty good case for why I believe MUG4N is right, I'm obviously not 100% certain.)


Original question

Often you just want to notify the user that something has occurred, but there's really no need for any input from them. In this common scenario, I sometimes see code like this:

MessageBox.Show("Something has occurred", "Something", MessageBoxButtons.OK);

This code, as we all know, causes a little pop-up window to appear with only an OK button. Now here's the thing: this code blocks (the UI thread). But in the vast majority of cases, it seems to me, if you only have an OK button, there's very little need to block. (Isn't the purpose of blocking typically to receive some input from the user? And if the user's only choice is "OK," in this typical case, isn't blocking pretty pointless?)

Obviously I could just write my own little form that does basically exactly what MessageBox.Show does, except that it returns nothing (no DialogResult) and doesn't block. But I was just wondering if something like this exists already that I didn't know about.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

爱你是孤单的心事 2024-09-08 12:36:35

您需要使用多线程来执行此任务,其中一个线程(主线程)将执行处理,另一个线程将用于显示消息框。

You need to use multi threading to perform this task in which one thread (the main thread) will do the processing and the other thread will be used to show the messagebox.

北渚 2024-09-08 12:36:35

向您的应用程序添加 NotifyIcon 怎么样?并显示气球提示?缺点是通知会在短时间内消失,但如果用户不需要采取行动,这可能对他们来说是最好的。

关于这个问题还有更多建议< /a>.

What about adding a NotifyIcon to your application and displaying a balloon tip? The down side is that the notification will disappear after a short time, but maybe that's best for your users if they don't need to take action.

There are more suggestions on this question.

七色彩虹 2024-09-08 12:36:35

我会尝试调用 MessageBox直接从 Win32 API 调用函数,例如:

using System.Runtime.InteropServices;

[DllImport("User32.dll")]
public static extern int MessageBox(int h, string m, string c, int type);

尝试使用空句柄和 APPLMODAL 类型。这可能有效。

What I would try is call the MessageBox function from the Win32 API directly, like :

using System.Runtime.InteropServices;

[DllImport("User32.dll")]
public static extern int MessageBox(int h, string m, string c, int type);

Try using a null handle, and the APPLMODAL type. That may work.

迟月 2024-09-08 12:36:35

这是一个老问题,但尽管如此...

1) Import IWshShell ('Windows Script Host Object Model')

Dim wsh As New IWshRuntimeLibrary.WshShell
wsh.Popup(String.Concat(Me.GetType.FullName, vbCrLf, _
Application.ExecutablePath)、0.75、“标题”、MessageBoxButtons.OKCancel 或 MessageBoxIcon.Question)

Jens...

It's an old question, but nevertheles...

1) Import IWshShell ('Windows Script Host Object Model')

Dim wsh As New IWshRuntimeLibrary.WshShell
wsh.Popup(String.Concat(Me.GetType.FullName, vbCrLf, _
Application.ExecutablePath), 0.75, "Title", MessageBoxButtons.OKCancel Or MessageBoxIcon.Question)

Jens...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文