Task.Run中的奇怪行为

本文关键字:Run Task | 更新日期: 2023-09-27 18:01:35

我正在尝试运行一个冗长的操作,结果显示在窗口中,而不会阻塞UI线程。我所拥有的是一个具有ListView控件的View,该控件绑定到我的ViewModel中的ObservableCollection。我还有一些其他绑定到两个数组的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值,即Years[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() {..}

Task.Run中的奇怪行为

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

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

这是你得到的。。。

10101010101010101010

这是由于代理关闭不充分造成的。为了结束它,写一个小的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())));
        }
    }

观察结果是。。。

01.2.3.4.5.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线程上进行调度。