线程和GUI调用问题

时间:2014-01-23 11:17:18

标签: c# multithreading

我有一种导出数据的方法。我通过一个新线程来做这件事,以便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放回原始线程。我需要的只是第一个线程“暂停”,然后在第二个线程结束后继续。欢迎提出意见。

4 个答案:

答案 0 :(得分:3)

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

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

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

答案 1 :(得分:3)

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

这样做:

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

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

答案 2 :(得分:-1)

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

答案 3 :(得分:-1)

试试这个:

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 Messaging循环,它基本上是一个循环,它遍历来自OS(和其他应用程序)的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();
    }
}