WPF与UI线程问题 - TaskFactory,CollectionView问题 - 语法噩梦

时间:2013-01-17 12:29:34

标签: wpf multithreading task-parallel-library async-await collectionview

请参阅我的伪解决方案的帖子底部。

我再一次完全坚持这一点。我已经花了好几个小时试图理解 - 是的,我可以让一个collectionviewsource工作得很好,没有任何关于线程代码的线程。

当我发现仅在页面上添加两个collectionviewsource导致线程问题时,想象一下我的震惊。昨晚我花了几个小时阅读C#5中的Async和MSDN的东西,但是今天我开始工作,我无法破译如何实现这一点。

下面的代码是我在抱怨之前做的最后一次尝试,因为我已经烧了,可能会有太多的工作时间试图理解如何做到这一点。我知道在启动另一个之前我需要一个collectionviewsource来完成,所以我尝试了Await Task.ContinueWith等尝试逐个链接。

正确填充线程中的两组任务似乎非常棘手,或者我仍然误解了一些有趣的东西。

如果有人可以建议他们如何在WPF UI上异步填充一些控件,我将非常感激。

应用程序本身是一个一次性的应用程序,链接到一个Access数据库,我正在尝试使用它在流程中足够流畅,以便在我们正确的代码库中实现它。我离它很远了!

更新了更完整的代码示例以及根据答案进行的调整:

Private Async Sub MainWindowLoaded(sender As Object, e As RoutedEventArgs) Handles MyBase.Loaded
InitializeComponent()

Dim personSetViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("personSetViewSource"), System.Windows.Data.CollectionViewSource)
Dim contactSetViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("contactSetViewSource"), System.Windows.Data.CollectionViewSource)

Dim personList = Await Task.Run(Function() personSet.personList)
personSetViewSource.Source = personList

Dim contactList = Await Task.Run(Function() contactSet.contactList)
contactSetViewSource.Source = contactList

End Sub`

ObservableCollectionEx类:

public class ObservableCollectionEx<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    using (BlockReentrancy())
    {
        NotifyCollectionChangedEventHandler collectionChanged = this.CollectionChanged;
        if (collectionChanged != null)
            foreach (NotifyCollectionChangedEventHandler nh in collectionChanged.GetInvocationList())
            {
                DispatcherObject dispObj = nh.Target as DispatcherObject;
                if (dispObj != null)
                {
                    Dispatcher dispatcher = dispObj.Dispatcher;
                    if (dispatcher != null && !dispatcher.CheckAccess())
                    {
                        NotifyCollectionChangedEventHandler nh1 = nh;
                        dispatcher.BeginInvoke(
                            (Action) (() => nh1.Invoke(this,
                                                       new NotifyCollectionChangedEventArgs(
                                                           NotifyCollectionChangedAction.Reset))),
                            DispatcherPriority.DataBind);
                        continue;
                    }
                }
                nh.Invoke(this, e);
            }
    }
}
}

请注意,由于需要事件覆盖,我无法将此类翻译为VB。 我试过的另一种变化,但再次违反线程所有权。这两个集合视图并没有产生解决方案:我不知道是不是因为底层集合不适合它,或者实际上它是不是以这种方式工作。我接近但没有雪茄。

 Dim CarePlanList = Task.Run(Function() CarePlanSet.CarePlanList)
    Dim rcpdList = Task.Run(Function() rcpdSet.rcpdList)

    Dim tasks() As Task = {CarePlanList, rcpdList}
    Dim t = New TaskFactory
    Await t.ContinueWhenAll(tasks, Sub()
                                       carePlanSetViewSource.Source = CarePlanList
                                       rcpdSetViewSource.Source = rcpdList
                                   End Sub)

我找到了一种方法,基于今天早上的反馈和研究相结合。鉴于WPF的STAThread模型,异步构建两个集合视图本身是不切实际的。然而,仅仅确保一个HAS完成并将一些异步从一个实体类中移出已经使这看起来合理。

相反,我启动了第一个任务,谁的底层类使用自己的异步方法构建它的数据。然后在允许第二次采集视图被触发之前测试它是否已完成。这样我就不用担心上下文或调度程序对象了。第二个集合不使用任何异步。

    Dim personList = Task(Of List(Of person)).Run(Function() personSet.personList)
    Dim contactList = Task(Of ObservableCollectionEx(Of contact)).Run(Function() contactSet.contactList)

    contactSetViewSource.Source = contactList.Result
    If contactList.IsCompleted Then personSetViewSource.Source = personList.Result

这是一个真正的概念研究实验项目。碰巧的是,我想要用这种方式构建两个这样的列表的想法并没有那么有用,但我确实看到能够异步编写数据量大的接口可能很方便。

1 个答案:

答案 0 :(得分:1)

您的两个代码示例都会立即出现问题。

在第一个你正在等待task1的时候,我假设有更多代码跟随,但是所有task1正在做的是开始什么基本上是火和忘记操作回UI线程({{1因此,没有真正产生任何异步等待。

在第二个问题中,主要问题似乎是您正在对Dispatcher.BeginInvoke个实例进行大量设置并将它们与延续相关联,但从未启动Task,这似乎是action2 Task的根。整个链条,因此根本没有活动。这类似于BackgroundWorker从未调用过RunWorkerAsync的内容。

为了让这个工作正常并避免让你的头部旋转,我建议首先编写整个块而不进行任何异步并验证所有内容是否按预期加载,但是要避免使用UI锁定。 Async / Await旨在添加到具有最小结构变化的代码中。使用Task.Run以及异步等待您可以使代码异步。

这是基本模式的一些伪代码,没有异步启动:

PersonSetList = LoadData1()
CVS1.Source = PersonSetList
ContactList = LoadData2()
CVS2.Source = ContactList

现在添加async:

PersonSetList = await Task.Run(LoadData1())
CVS1.Source = PersonSetList
ContactList = await Task.Run(LoadData2())
CVS2.Source = ContactList

现在要做的是启动一个任务来加载人员数据并立即从WindowLoaded方法返回,允许UI继续渲染。加载该数据后,它将继续到原始线程上的下一行,并将数据推送到UI(这可能会在渲染时减慢UI速度)。之后,它将对联系人数据执行相同操作。请注意,显式不需要Dispatcher,因为await返回UI线程以便您完成其继续。