Task.Run中的奇怪行为

时间:2014-05-03 16:32:35

标签: c# wpf task

我正在尝试运行一个冗长的操作,结果显示在窗口中,而不会阻止UI线程。我所拥有的是View,其ListView控件与ObservableCollection中的ViewModel绑定。我还有一些其他TextBlock控件绑定到两个数组。在Task内的长时间操作之前运行的任何内容都会显示,之后的任何内容都不会显示。这是我的代码,可以帮助您理解我的意思:

四年:

<TextBlock TextWrapping="Wrap" Grid.Row="1" 
           Style="{DynamicResource SectionBodyTextStyle}">
    <Run Text="{Binding Years[1]}"/>
    <Run Text=":"/>
</TextBlock>
<TextBlock TextWrapping="Wrap" Grid.Row="2" 
           Style="{DynamicResource SectionBodyTextStyle}">
    <Run Text="{Binding Years[2]}"/>
    <Run Text=":"/>
</TextBlock>
<TextBlock TextWrapping="Wrap" Grid.Row="3" 
           Style="{DynamicResource SectionBodyTextStyle}">
    <Run Text="{Binding Years[3]}"/>
    <Run Text=":"/>
</TextBlock>

每年保留计数的四个字段。

<TextBlock Text="{Binding YearCounts[0]}" TextWrapping="Wrap" 
           Grid.Column="1" Margin="10,0,0,0"/>
<TextBlock Text="{Binding YearCounts[1]}" TextWrapping="Wrap" 
           Grid.Column="1" Grid.Row="1" Margin="10,0,0,0"/>
<TextBlock Text="{Binding YearCounts[2]}" TextWrapping="Wrap" 
           Grid.Column="1" Grid.Row="2" Margin="10,0,0,0"/>
<TextBlock Text="{Binding YearCounts[3]}" TextWrapping="Wrap" 
           Grid.Column="1" Grid.Row="3" Margin="10,0,0,0"/>

ListView保存每条记录的信息:

<ListView>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Report Number" 
                            DisplayMemberBinding="{Binding RepNum}"/>
            <GridViewColumn Header="Employee ID" 
                            DisplayMemberBinding="{Binding EmployeeId}"/>
            <GridViewColumn Header="Date" 
                            DisplayMemberBinding="{Binding Date}"/>
            <GridViewColumn Header="Time" 
                            DisplayMemberBinding="{Binding Time}"/>
        </GridView>
    </ListView.View>
</ListView>

到目前为止,没有什么不寻常的。但是,这里的代码与他们有关。

属性:

    private string[] _years;
    public string[] Years
    {
        get { return _years; }
        private set
        {
            if (_years == value)
            {
                return;
            }

            _years = value;
            OnPropertyChanged("Years");
        }
    }

    private int[] _yearCounts;
    public int[] YearCounts
    {
        get { return _yearCounts; }
        private set
        {
            if (_yearCounts == value)
            {
                return;
            }

            _yearCounts = value;
            OnPropertyChanged("YearCounts");
        }
    }

    private ObservableCollection<RecordModel> _missingCollection;
    public ObservableCollection<RecordModel> MissingCollection
    {
        get { return _missingCollection; }
        private set
        {
            if (_missingCollection == value)
            {
                return;
            }

            _missingCollection = value;
            OnPropertyChanged("MissingCollection");
        }
    }

构造

public MissingReportsViewModel()
{
    YearCounts = new int[4];
    Years = new string[4];

    Task.Run(() =>
    {
        SetYears();
        MissingCollection = new AccessWorker().GetMissingReports();
        SetYearCounts();
    });
}

ViewModel的方法:

private void SetYears()
{
    for (int i = 0; i < 4; i++)
    {
        Years[i] = DateTime.Now.AddYears(-i).Year.ToString();
    }
}

private void SetYearCounts()
{
    for (int i = 0; i < 4; i++)
    {
        YearCounts[i] =  MissingCollection.Where(item => item.RepNum.Substring(0, 4).Equals(Years[i]))
                                          .ToList().Count();

    }
}

还有来自访问工作者的方法,但代码相当冗长。它基本上连接到Access数据库并获取一些数据。

所以,我的问题是,如果我将MissingCollection = new AccessWorker().GetMissingReports();部分之前的任何方法放在Task.Run()之内或之外,它们将显示在UI上。但是,如果我在该部分之后放置任何内容,它将无法在UI中显示。如果以下方法在Task.Run范围内,则无关紧要,结果相同。我已经检查过并且方法产生了正确的值,它们只是永远不会进入UI。我根本不明白这两者如何产生如此不同的结果:

// First -  Years get displayed.
// Second - After a little while data gets displayed
// Third - Counts never get displayed.
Task.Run(() =>
{
    SetYears();
    MissingCollection = new AccessWorker().GetMissingReports();
    SetYearCounts();
});


// First -  After a while, data gets displayed.
// Second - Years and counts do not get displayed.
Task.Run(() =>
{
    MissingCollection = new AccessWorker().GetMissingReports();
    SetYears();
    SetYearCounts();
});

我显然做错了什么,但我无法弄清楚是什么。我尝试过调用UI线程,但是没有做任何事情。

修改

当我尝试为我的YearsCount数组绑定调用UI线程的更新时,我得到一个奇怪的超出范围的异常。这是代码:

private void Initialize()
{
    SetYears();
    Task.Run(() =>
    {
        MissingCollection = new AccessWorker().GetMissingReports();
        SetYearCounts();
    });
}

private void SetYearCounts()
{
    for (int i = 0; i < 4; i++)
    {
        Application.Current.Dispatcher.BeginInvoke(
            DispatcherPriority.Background, 
            new Action(() => YearCounts[i] =  MissingCollection.Where(
                    item => item.RepNum.Substring(0, 4).Equals(Years[i])).ToList().Count()));
    }
}

当我单步执行它时,它将遍历YearCounts [i]的每个索引,跳出SetYearCounts()返回Task.Run(),然后跳回SetYearsCounts()并使用最后i值,年份[i]为4,显然会超出范围异常。

当我在没有Task.Run()的情况下运行代码时,这些都不会发生。它只是冻结UI,直到操作完成。

如果我这样做:

private void Initialize()
{
    SetYears();
    Task.Run(() =>
    {
        MissingCollection = new AccessWorker().GetMissingReports();
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
            new Action(() => SetYearCounts()));
    });
}

...并在调试窗口中分配每个值:

Debug.WriteLine(YearCounts[i]);

...它会在那里写输出,但不在UI窗口上。我认为它与数组只报告自己的变化而不是项目本身的变化这一事实有关。我可以在调试窗口中看到只报告数组的初始初始化,但没有更改项目。然而,奇怪的是,可观察集合之前的任何事物都会得到更新,并且之后的任何事情都不会更新。当可观察集合调用更改时,它可能与查看其数据上下文的视图有关。

根据请求,此处为GetMissingReports()声明部分:

public ObservableCollection<RecordModel> GetMissingReports() {..}

1 个答案:

答案 0 :(得分:1)

对于有关下标超出范围错误的部分,您可以创建一个仅执行此操作的小型WPF应用程序...

    public MainWindow()
    {
        InitializeComponent();
        for (int i = 0; i < 10; i++)
        {
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
                new Action(() => Console.WriteLine(i.ToString())));
        }
    }

这就是你得到的......

10 10 10 10 10 10 10 10 10 10

这是由于代表关闭不充分造成的。要关闭它,写一个只做这个的小WPF应用程序......

    public MainWindow()
    {
        InitializeComponent();
        for (int i = 0; i < 10; i++)
        {
            int i1 = i;
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
                new Action(() => Console.WriteLine(i1.ToString())));
        }
    }

并观察结果是......

0 1 2 3 4 五 6 7 8 9

区别在于'关闭'。您的Action委托发生在循环 AND 正在访问循环控制变量中。使用闭包将使例外消失。

对于问题的同步部分,并根据您所写的内容,它看起来像竞争条件。如果您更改此代码块...

private void Initialize()
{
    SetYears();
    Task.Run(() =>
    {
        MissingCollection = new AccessWorker().GetMissingReports();
        SetYearCounts();
    });
}

到此......

    private void OtherInitialize()
    {
        SetYears();
        Task task = new Task(() => { MissingCollection = new AccessWorker().GetMissingReports(); });
        task.ContinueWith((result) => { SetYearCounts(); });
        task.Start();
    }

...然后你的'岁月'会按预期同步,而不是创造正在发生的竞争条件。但是,你仍然需要在UI线程上发送。