2

See bottom of post for my pseudo solution.

Once again I'm completely and utterly stuck on this. I've burned hours trying to understand - and yes I can get a single collectionviewsource to work beautifully with nothing about threading on the code behind.

Imagine my shock when I found merely adding two collectionviewsources on the page causes threading issues. I've spent a few hours last night reading Async in C#5 and the MSDN stuff however I get into work today and I can't decipher how to make this happen.

The code below is the last attempt I've made before whining for help as I've burnt, possibly, a bit too much work time on attempting to understand how to do this. I understand that I need one collectionviewsource to complete before starting the other, so I tried Await Task.ContinueWith etc to try and chain one after the other.

Lining up both sets of tasks in the threads correctly seems to be quite tricky, or I'm still misunderstanding something fundemental.

If anyone can advise how they would asynchronously populate a few controls on a WPF UI I would be very grateful.

The application itself is a throwaway application, linked to an Access database that I'm using to try and become fluent enough in threading to implement it in our proper code base. I'm long way off that!

Updated with more complete code samples and the adjustments made according to answers:

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`

The ObservableCollectionEx class:

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);
            }
    }
}
}

Please note, I can't translate this class to VB due to requiring an Event Override. Another variation I've tried, but falls foul of thread ownership again. The two collectionviews thing isn't yielding to a solution: I don't know if it's because the underlying collection isn't good for it or whether in reality it wasn't meant to work that way. I get close but no cigar.

 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)

I found a way to do it, based on a combination of feedback and research this morning. Building the two collectionviews asynchronously itself is somewhat impractical given the STAThread model of WPF. However, merely ensuring one HAS completed and shifting some of the async out of one entity class has made this plausible.

Instead I fire off the first task, who's underlying class does build it's data with its own Async method. Then test to see if it has completed before allowing the second collectionview to be fired off. This way I don't need to worry about context or dispatcherobjects. The second collection does not use any async.

    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

This is an experimental project for concept research really. As it happens, the idea I'd want two such lists built this way isn't as useful as all that but I do see where being able to compose a data heavy interface asynchronously could be handy.

4

1 回答 1

1

您的两个代码示例都存在立即跳出的问题。

在您正在等待的第一个中task1,我假设后面有更多代码,但task1所做的只是启动基本上是触发并忘记操作返回 UI 线程 ( Dispatcher.BeginInvoke),因此不会真正产生任何异步等待。

其次,主要问题似乎是您正在进行大量Task实例设置并将它们与延续链接,但从未启动action2 Task,这似乎是整个链的根,因此根本没有任何活动。这类似于你得到的BackgroundWorker从未RunWorkerAsync调用过的结果。

为了让它正常工作并避免让你头晕目眩,我建议首先编写整个块而不进行任何异步并验证所有内容是否按预期加载,但要避免 UI 锁定。Async/Await 旨在以最小的结构更改添加到类似的代码中。与 async 和 await 一起使用Task.Run,您可以使代码异步。

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

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

现在添加异步:

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

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

于 2013-01-17T15:34:35.213 回答