WPF,ObservableCollections和多线程...我的选择是什么?

时间:2012-02-23 15:24:49

标签: wpf vb.net multithreading

我最近编写了我的第一个VB WPF应用程序,用Quickbooks执行一些自动化任务。这是一个挑战,因为我以前没有VB或Quickbooks的经验。除此之外,我还不了解线程模型以及我在此过程中可能遇到的问题。我基本上在1个线程中编写了整个应用程序... UI线程。我已经明白,虽然这“有效”,但它会导致长时间运行的任务停止UI线程的问题。我开始研究多线程,现在遇到了问题,因为我使用的是UI绑定 ObservableCollections ,它本身不支持在辅助线程上更新OC。我发现了一些关于这个主题的帖子,比如Mirality的this one,我会研究下一个。

在我对整个应用程序进行大量重写之前,我希望有一种更简单的方法来完成我正在尝试做的事情。我想查看多线程的唯一原因是因为我想显示一个“正在加载...”窗口,有或没有特定于任务的消息(即“从Quickbooks获取所有供应商......”等),具体取决于什么更容易。如果有一种简单的方法可以快速“任务切换”来更新UI线程,然后继续执行可能解决我的问题的其他任务,但我不知道该怎么做。

这是一个简单的例子来说明问题。我有一个自定义应用程序类,它在 AppStartup 方法中执行启动任务。

Partial Public Class MyApp
    Inherits System.Windows.Application

    Private _qbclient As QBClient
    Private _vendorListWindow As VendorListWindow
    Private _splashScreen As StartupSplash

    '======================================================
    '   AppStartup Method
    '       Initialize the application
    '======================================================
    Public Sub AppStartup()
        _vendorListWindow = New VendorListWindow()
        _vendorListWindow.IsEnabled = False

        _splashScreen = New StartupSplash()
        _splashScreen.Owner = _vendorListWindow
        _splashScreen.Show()

        _splashScreen.progressMessage.Content = "Downloading all items from Quickbooks..."
        Me.GetQBClient().GetAllItems()

        _splashScreen.progressMessage.Content = "Downloading all vendors from Quickbooks..."
        Me.GetQBClient().GetAllVendors()

        _splashScreen.progressMessage.Content = "Locating vendors with items..."
        Me.GetQBClient().FlagVendorsWithItems()

        _splashScreen.progressMessage.Content = "Adding list filters..."
        _vendorListWindow.AddListFilters()

        _splashScreen.Close()
        _vendorListWindow.Show()
        _vendorListWindow.IsEnabled = True

    End Sub
End Class

在上面的示例中, StartupSplash 只是一个基本窗口,没有用于启动启动画面的框架。在应用程序的其他时间,我有另一个基本的加载窗口,基本上做同样的事情,但看起来略有不同。

在任何一种情况下,我都会看到启动画面,但 progressMessage 文本块的内容永远不会更新。显然,这是因为UI线程忙于我要求它做的任务。在所有情况下,我都会在调用下一个任务之前从长时间运行的任务返回,因此有足够的时间将更新推送到UI然后继续。这甚至是可能的,还是我必须承担多线程应用程序的艰巨任务?

谢谢!

3 个答案:

答案 0 :(得分:1)

在C#中,但不应该太难翻译... 请注意,Thread.Sleep函数可模拟您长时间运行的任务。 此外,使用TPL,您不必将它们链接在一起。您可以一次性单独开始每个长时间运行(如果它们不相互依赖)。

using System.Threading.Tasks;

        txtProgress.Text = "Long-run 1";
        Task.Factory
            .StartNew(() => Thread.Sleep(2000))
            .ContinueWith(
                task => txtProgress.Text = "Long-run 2", TaskScheduler.FromCurrentSynchronizationContext())
            .ContinueWith(task => Thread.Sleep(2000))
            .ContinueWith(
                task => txtProgress.Text = "Long-run 3", TaskScheduler.FromCurrentSynchronizationContext())
            .ContinueWith(task => Thread.Sleep(2000))
            .ContinueWith(task => txtProgress.Text = "Done.", TaskScheduler.FromCurrentSynchronizationContext())
        ;

答案 1 :(得分:0)

如果您了解Windows窗体,您可能知道DoEvents()方法,它通常处理消息,以便您的UI可以不时更新。 WPF没有这个,但有一个解决方法(从C#转换,希望这有效):

Private Sub DoEvents()
    Dim f As New DispatcherFrame()
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, DirectCast(Function(arg As Object) Do
        Dim fr As DispatcherFrame = TryCast(arg, DispatcherFrame)
        fr.[Continue] = [True]
    End Function, SendOrPostCallback), f)
    Dispatcher.PushFrame(frame)
End Sub

您可以在操作之间调用此方法,UI应处理事件并使用挂起的更改进行更新。但是,在获取数据时,这仍然会使您的UI无响应。但是因为它是一个闪屏,这对你来说可能是可以接受的。

答案 2 :(得分:0)

经过多次在线搜索和头部刮擦,我终于解决了它。最后这很简单。您需要做的就是将 System.Windows.Forms DLL包含到应用程序引用中,然后调用内置的 System.Windows.Forms.Application.DoEvents()方法。关于这一切的令人沮丧的部分是关于如何做事的好的文档很少。 MSDN库充满了关于可用的技术信息,但没有那么多如何使用它。希望这可以帮助其他需要类似功能的人。

以下是我上面的代码段,修改为与 System.Windows.Forms

一起使用
Imports System.Windows.Forms 'This must be included in the assembly references...doesn't work otherwise

Partial Public Class MyApp
    Inherits System.Windows.Application

    Private _qbclient As QBClient
    Private _vendorListWindow As VendorListWindow
    Private _splashScreen As StartupSplash

    '======================================================
    '   AppStartup Method
    '       Initialize the application
    '======================================================
    Public Sub AppStartup()
        _vendorListWindow = New VendorListWindow()
        _vendorListWindow.IsEnabled = False

        _splashScreen = New StartupSplash()
        '_splashScreen.Owner = _vendorListWindow
        _splashScreen.Show()

        _splashScreen.progressMessage.Content = "Downloading all items from Quickbooks..."
        Me.GetQBClient().GetAllItems()
        Forms.Application.DoEvents()
        System.Threading.Thread.Sleep(100) 'This is here so that the text shows up even if the step is really quick

        _splashScreen.progressMessage.Content = "Downloading all vendors from Quickbooks..."
        Me.GetQBClient().GetAllVendors()
        Forms.Application.DoEvents()
        System.Threading.Thread.Sleep(100) 'This is here so that the text shows up even if the step is really quick

        _splashScreen.progressMessage.Content = "Locating vendors with items..."
        Me.GetQBClient().FlagVendorsWithItems()
        Forms.Application.DoEvents()
        System.Threading.Thread.Sleep(100) 'This is here so that the text shows up even if the step is really quick

        _splashScreen.progressMessage.Content = "Adding list filters..."
        _vendorListWindow.AddListFilters()

        _splashScreen.Close()
        _vendorListWindow.Show()
        _vendorListWindow.IsEnabled = True

    End Sub
End Class