【问题标题】:Thread and GUI with invoke issue带有调用问题的线程和 GUI
【发布时间】:2014-02-13 21:48:29
【问题描述】:

我有一个导出数据的方法。我通过一个新线程执行此操作,以便 GUI 保持响应。最后它会打开一个SaveFileDialog,如果没有调用它就无法工作。通过以下修改,它可以正常工作,但 GUI 再次没有响应。有什么线索吗?

private void button1_Click(object sender, EventArgs e)
{
   Thread thread = new Thread(method);
   thread.Start();
}
public void medhod()
{
   if (this.InvokeRequired)
   {
       Invoke(new MethodInvoker(delegate() { method(); }));
   }
   else
   {
       //Code
       //SaveFileDialog
   }
}

*编辑:另一种方法是将导出代码留在新线程中,并将SaveFileDialog 放回原始线程。我只需要第一个线程“暂停”,然后在第二个线程结束后继续。欢迎提出想法。

【问题讨论】:

  • 非 UI 线程和模式对话框不能很好地协同工作...
  • 对于大多数任务来说,直接使用线程实际上已经过时了。就使用Task 设施而言,建议的技术是TPL (msdn.microsoft.com/en-us/library/dd537609%28v=vs.110%29.aspx)。尽管就像@James 说的那样,它并不能解决您的问题
  • 我通过一个新线程执行此操作,以便 GUI 保持响应我在任何地方都看不到!你只是在 UI 线程中做所有事情。
  • 这也是一个 GUI 线程吗?无论如何如何解决这个问题?
  • 不要为整个操作执行调用,仅用于保存对话框。也就是说,像往常一样进行处理,并且仅在最后执行 Invoke 以创建对话框并执行您需要的任何操作。

标签: c# multithreading


【解决方案1】:

尝试使用异步的 BeginInvoke() 而不是同步的 Invoke 请参阅What's the difference between Invoke() and BeginInvoke() 以获得更好的论点。

【讨论】:

  • 如果我只是将Invoke(new MethodInvoker(delegate() { ws_uptime_query(); })); 更改为BeginInvoke(new MethodInvoker(delegate() { ws_uptime_query(); })); 它仍然没有响应。你能分享一些示例代码吗?
  • BeginInvoke 不会在这里改变任何东西。
【解决方案2】:

试试这个:

void btnClick(object sender, EventArgs e)
{
    var t = new Thread(doStuff);
    t.SetApartmentState(ApartmentState.STA);
    t.Start();
}

void doStuff()
{
    SaveFileDialog sfd = new SaveFileDialog();

    sfd.ShowDialog();   
}

SaveFileDialog 在单独的线程中运行(它必须有一个单线程单元),它不会阻塞应用程序的 UI 线程。但是,要非常小心你所做的一切,它可能非常不稳定。

基本问题是您的应用程序正在运行一个 Windows 消息循环,它基本上是一个循环遍历来自操作系统(和其他应用程序)的 Windows 消息的循环。如果由于某种原因循环被卡住,应用程序将变得无响应(当您单击鼠标时,操作系统会向您的消息队列发送 WM_MOUSEDOWN 和更多方法,必须由消息循环取消填充才能执行任何操作)。使用ShowDialog 方法正是循环卡住的一种方式——您的表单无法再处理任何消息,因为它永远没有机会处理。

现在,Invoke 所做的是,它将您要调用的方法添加到目标表单上的另一个队列中。窗体在 Windows 消息传递循环期间获得的下一次机会,它执行调用队列中的所有项目 - 再次,消息传递循环卡住了。

现在,对话框如何接收 Windows 消息?很简单,在实践中,它会创建自己的 WM 循环。这只是另一个while 循环,它只会在模式对话框关闭时终止——阻塞父表单(或者更确切地说,应用程序)的消息循环。

上面代码的问题是它可能会从我们的父窗口窃取消息循环。解决方案是通过将所有者显式传递给ShowDialog,显式创建一个带有新消息循环的新窗口:

void doStuff()
{
    NativeWindow nw = null;

    try
    {
        nw = new NativeWindow();

        nw.CreateHandle(new CreateParams());
        SaveFileDialog sfd = new SaveFileDialog();

        sfd.ShowDialog(nw);
    }
    finally
    {
        if (nw != null)
            nw.DestroyHandle();
    }
}

【讨论】:

  • 您不应该在非 UI 线程中运行 UI 组件。 “对你所做的一切都非常小心,它可能非常不稳定” - 那为什么还要建议呢?
  • @James 因为您只需要小心处理两个 UI 线程之间的交互 ​​- 这就是 Invoke 方法最终发挥作用的地方。
  • 认为您错过了我评论的重点,尝试在任何其他线程上修改 任何 类型的 UI 组件永远不是一个好主意因为所有 UI 组件都具有线程关联性,所以创建它的那个。
  • @James:是的,但我正在自己的线程上创建 SaveFileDialog。这就是重点——只要它从未在原始表单 UI 线程中使用,并且原始表单 UI 组件未在 SFD 线程中使用,一切都很好。毕竟,您在计算机上运行了一百个应用程序,每个应用程序都有自己的 UI 线程;过程无关紧要,只有窗户才重要。
  • @James:不,每个线程都有自己的消息队列。这是因为如果当前线程没有,ShowDialog 方法确实会创建一个新的应用程序线程上下文(上下文是 ThreadStatic)。这实际上就是为什么在两个线程上使用相同的表单是一个巨大的危险——你正在使用相同的窗口句柄在两个线程上接收消息;创建一个明确的新NativeWindow 可以轻松解决这个问题。
【解决方案3】:

问题是在非 UI 线程中运行任何类型的 UI 组件通常是一个糟糕的想法 - 尤其是模式对话框。

相反,将实际的后台处理代码放入另一个线程,完成后回调到 UI 线程并启动保存对话框。 TPL 让这类事情变得非常琐碎,例如

Task.Factory.StartNew(() => {
    // do background processing
}).ContinueWith((task) => {
    // show save dialog
}, TaskScheduler.FromCurrentSynchronizationContext());

【讨论】:

  • 错误:当前的 SynchronizationContext 不能用作 TaskScheduler。
  • 看起来有一个known issue 与 .NET 4.0 中的任务调度程序导致同步上下文设置为 null。按照各种解决方法的链接或升级到 4.5。
  • 谢谢,会检查的。同时,我将使用 Luaan 的解决方案。
【解决方案4】:

您的问题可能是 Luaan 评论所指出的。您有很长的操作要放入线程中,但随后您将整个操作调用到 UI 线程中,它会在一段时间内阻塞 UI 线程。

这样做:

private void button1_Click(object sender, EventArgs e)
{
    (new Thread(method)).Start();
}
private void method()
{
    //Code
    Invoke(() =>
    {
        //SaveFileDialog
    });
}

您不需要检查InvokeRequired,因为无论如何它都是必需的。您使用它的方式是定义方法的模式,可以从任一线程调用。但在这种情况下,它通常包含与 UI 控件交互的非常短的操作。

【讨论】:

  • 您不需要检查 InvokeRequired,因为无论如何都会需要它” - 如果也直接从 UI 调用 method,则不需要。
  • @James,我同意,但很明显,OP 隐藏了他不想在 UI 线程中运行的长操作。
  • 一个小问题。如果我执行上述操作就可以了,但它会在实际打开SaveFileDialog 之前运行Code 部分两次。因此,当到达Code 的末尾时,它会再次启动它。对此有什么想法吗?
  • 是的,我做到了。我像鼹鼠一样瞎。再次感谢。
  • 不客气。更重要的一件事是不要创建方法public
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-02
  • 2011-03-26
  • 1970-01-01
  • 2011-01-28
  • 1970-01-01
相关资源
最近更新 更多