C# 在非UI线程上运行模式对话框

C# 在非UI线程上运行模式对话框,c#,.net,winforms,multithreading,modal-dialog,C#,.net,Winforms,Multithreading,Modal Dialog,我正在编写一个简单的数据用户界面,使用标准的.Net数据绑定从SQL Server到类型化数据集 我有一个重载按钮,它在所有DataAdapter上调用Fill,从数据库中获取新数据(以防其他用户更改数据) 这需要一些时间,在此期间用户界面被冻结。它必须在UI线程上运行,或者数据绑定事件处理程序抛出跨线程异常 我想在UI线程连接到数据库时,在后台线程上显示一个模式“请等待”对话框(以便可以设置动画) 如何在非UI线程上显示模式对话框 编辑:我知道最佳做法是在后台运行操作,但由于数据绑定事件,我

我正在编写一个简单的数据用户界面,使用标准的.Net数据绑定从SQL Server到类型化数据集

我有一个重载按钮,它在所有DataAdapter上调用
Fill
,从数据库中获取新数据(以防其他用户更改数据)

这需要一些时间,在此期间用户界面被冻结。它必须在UI线程上运行,或者数据绑定事件处理程序抛出跨线程异常

我想在UI线程连接到数据库时,在后台线程上显示一个模式“请等待”对话框(以便可以设置动画)

如何在非UI线程上显示模式对话框



编辑:我知道最佳做法是在后台运行操作,但由于数据绑定事件,我不能这样做。您应该做相反的事情。在后台线程上运行长时间运行的进程,并让UI线程自由响应用户操作


如果要在处理过程中阻止任何用户操作,可以使用许多选项,包括模式对话框。后台线程完成处理后,您可以将结果通知主线程

数据绑定事件中运行的代码需要与UI解耦,可能需要使用某种数据传输对象

using( var frmDialog = new MyPleasWaitDialog() ) {
    // data loading is started after the form is shown
    frmDialog.Load += (_sender, _e) {
        // load data in separate thread
        ThreadPool.QueueWorkItem( (_state)=> {
            myAdapter.Fill( myDataSet );
            // refresh UI components in correct (UI) thread
            frmDialog.Invoke( (Action)myDataControl.Refresh );
            // close dialog
            frmDialog.Invoke( (Action)frmDialog.Close() );
        }
    }

    // shows dialog
    frmDialog.ShowDialog( this );
}
然后,您可以在单独的线程或
BackgroundWorker
中运行查询操作,并保持UI线程不变

编辑:解决这个问题的最快方法是使用
invokererequired
.Invoke
让事件在它们自己的委托中运行。这将为方法提供UI上下文。我的同事这样做就像过时了一样,这让我非常恼火,因为这样做很少是个好主意。。。但是如果你想要一个快速的解决方案,这将是可行的。(我不在上班,所以我身上没有样品,我会想办法的。)


编辑2:我不确定你的要求是否可行。我制作了一个示例应用程序,它在另一个线程中创建了一个模态对话框,结果它是非模态的。您是否可以使用其他控件或一组控件来指示进度更改,而不是使用模式对话框,很可能直接在同一表单上?

下面是一个使用BackgroundWorker加载数据并运行用户友好表单以显示“加载记录”或类似内容的示例

    public void Run()
    {
        bgWorkrFillDS = new BackgroundWorker();
        bgWorkrFillDS.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorkrFillDS_RunWorkerCompleted);
        bgWorkrFillDS.DoWork += new DoWorkEventHandler(bgWorkrFillDS_DoWork);
        bgWorkrFillDS.RunWorkerAsync();
    }

    void bgWorkrFillDS_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker bgWrkrFillDS = (BackgroundWorker)sender as BackgroundWorker;
        if (bgWrkrFillDS != null)
        {
            // Load up the form that shows a 'Loading....'
            // Here we fill in the DS
            // someDataSetAdapter.Fill(myDataSet);
        }
    }


    void bgWorkrFillDS_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // Hide or unload the form when the work is done
    }
希望这有助于。。。 当心,
Tom。

我通过创建一个新的数据集,在后台加载,然后在UI线程上调用
DataSet.Merge
解决了这个问题。谢谢大家的建议,这导致了这个解决方案


作为一个额外的好处,它的运行速度比以前快得多(在后台调用
Fill
,只在没有打开网格的情况下才起作用)。有人知道为什么吗?

正如我在问题中解释的,我不知道。我很清楚这是最好的做法。我想你还是可以的。这只是意味着后台线程不应该直接更新数据。检索它们、打包它们并委托给主线程更新UII我理解您的困境,但我仍然认为我建议的方法比替代方法更好(麻烦更少)。有没有简单的方法可以做到这一点,并且仍然使用标准的数据绑定和更新事件?我正在努力快速完成这个项目。有没有简单的方法可以做到这一点,并且仍然使用标准的数据绑定和更新事件?我正在努力尽快完成这个项目。编辑:我不能将
Invoke
调用放在数据网格中。Re:Edit 2:你很可能是对的。如果这是一个简单的问题,我就不会在这里问了。至于在窗体上放置控件,更糟糕的是,完全不可能在窗体上有其他线程拥有的控件。对,但是如果实际处理是在其他线程中进行的,那么这些控件只是窗体上的正常控件,在与表单相同的线程中。如果实际处理在不同的线程上,我可以在UI线程上调用
ShowDialog
,并生成一个模式表单。您需要加载不同数据集中的数据,该数据集未绑定到datagrid,然后将datagrid重新绑定到新数据集。和/或尝试在dataset/datatable上使用“BeginLoadData()”和“EndLoadData”。正如我在问题中试图解释的那样,
myAdapter.Fill
在后台线程上调用时在网格的数据绑定事件处理程序中抛出InvalidOperationException。在表上调用
BeginLoadData
没有帮助。因此,您需要填充另一个数据集,然后将datagrid重新绑定到“新建”(填充)数据集实例。顺便说一下,在添加匿名事件处理程序时,您可以编写
EventName+=委托{code}(注意缺少括号),这将隐式声明参数。请阅读问题。在后台线程上调用
someDataSetAdapter.Fill
将引发InvalidOperationException。