在列表中异步更新多个属性

时间:2013-12-24 21:09:37

标签: c# .net wpf task-parallel-library async-await

我正在创建一个ViewModel对象的ObservableCollection,其中每个对象在初始化时都有几个要完成的任务。

我在父视图模型中将这些添加到像这样的ObservableCollection中:

public async void ButtonPressCommandHandler() 
{
    for (int i = 0; i < 25; ++i)
    {
        var testViewModel = new TestViewModel();
        await testViewModel.Initialize();
        TestViewModels.Add(testViewModel);
    }
}

只需按一下按钮或其他一些事件即可调用该循环。

这是测试视图模型代码:

public class TestViewModel : ViewModelBase
{
    private string _taskOne;
    public string TaskOne
    {
        [DebuggerStepThrough]
        get { return _taskOne; }
        set
        {
            if (value != _taskOne)
            {
                _taskOne = value;
                RaisePropertyChanged(() => TaskOne);
            }
        }
    }

    private string _taskTwo;
    public string TaskTwo
    {
        [DebuggerStepThrough]
        get { return _taskTwo; }
        set
        {
            if (value != _taskTwo)
            {
                _taskTwo = value;
                RaisePropertyChanged(() => TaskTwo);
            }
        }
    }

    public async Task Initialize()
    {
        TaskOne = await TaskOneAsync();
        TaskTwo = await TaskTwoAsync();
    }

    private Task<string> TaskOneAsync()
    {
        return Task.Factory.StartNew(() =>
        {
            Thread.Sleep(100);
            return "Task one";
        });
    }

    private Task<string> TaskTwoAsync()
    {
        return Task.Factory.StartNew(() =>
        {
        var random = new Random();
        int randomNumber = random.Next(500, 5000);
        Thread.Sleep(randomNumber);
        return "Task two: " + randomNumber;
            Thread.Sleep(randomNumber);
            return "Task two: " + randomNumber;
        });
    }
}

(我知道我可以在构造函数中调用init工作,但这更接近我真正需要做的事情。)

在我看来,我有ListViewListView.ItemTemplate只显示包含TaskOneTaskTwo属性的文本块。它是ItemSource绑定到TestViewModels

我看到的是,对于创建的25个对象中的每一个,TaskOneTaskTwo属性都同时出现,并且每个对象仅在两个任务完成后显示。

如果我从await删除Initialize()并让Initialize()返回void(这是UI线程上的IS),行为会更好 - 我看到所有TaskOne属性都非常快速,然后TaskTwo属性开始填写。但它们显示的随机值是错误的 - 有许多重复,它们似乎以块或4或5填充列表(很难说) 。

非测试代码中的完整目标是更新与TaskTwo绑定的进度指示器。这样的事情(来自TestViewModel代码):

public async Task Initialize()
{
    TaskOne = await TaskOneAsync();
    Loading = true;
    TaskTwo = await TaskTwoAsync();
    Loading = false;
}

所有对象立即加载到列表中,然后在完成时异步更新其TaskOneTaskTwo属性,并根据TaskTwo中的工作更新进度指示器。但到目前为止,没有运气让它发挥作用。

编辑:添加了更好的示例代码和解释

1 个答案:

答案 0 :(得分:0)

首先,编写代码的方式结果应该如你所描述的那样。

  

我看到的是,对于创建的25个对象中的每一个,TaskOne和TaskTwo属性都同时出现,并且只有在两个任务完成后才显示e sadfach对象。

await testViewModel.Initialize();等待Initialize方法,后者依次awaits执行这两项任务。这意味着只有在两个任务都已完成后才添加项目(TestViewModels.Add(testViewModel);)。

其次,如果您没有await Initialize,您可以在上一次运行完成之前再次调用它,这意味着您可以并行运行2个任务(使用StartNew) 。这将导致几个Random实例彼此非常接近地创建。 Random类使用基于时间的种子来生成值,在这种情况下,种子将是相同的,因此相同的“随机”结果。来自随机MSDN文章:

  

默认种子值源自系统时钟并具有有限的分辨率。因此,通过调用默认构造函数紧密连续创建的不同Random对象将具有相同的默认种子值,因此将生成相同的随机数集。使用单个Random对象生成所有随机数可以避免此问题。您还可以通过修改系统时钟返回的种子值,然后将此新种子值显式提供给Random(Int32)构造函数来解决此问题

最后,我不鼓励在Task.Factory.StartNew环境中同时使用Thread.Sleepasync-await。您可能应该使用Task.Runawait Task.Delay代替(我知道这是一个示例代码)。