在Task.Run奇怪的行为(Strange behavior in Task.Run)

2019-10-29 01:41发布

我试图运行的结果显示在窗口中的冗长的操作,而不会阻塞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。 不过,如果我那部分后放置任何物品,它不会显示在用户界面。 不要紧,下面的方法是内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线程,但没有做任何事情。

编辑:

当我尝试调用一个更新到我的UI线程YearsCount阵列绑定,我得到一个奇怪的超出范围的异常。 下面的代码:

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值,是4年[I],其中,显然,投出范围的异常。

当我运行的代码,而无需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() {..}

Answer 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 5 6 7 8 9

所不同的是“关闭”。 你的Action委托进行一个循环内,并且在访问循环控制变量。 使用关闭将使得异常消失。

对于这个问题的同步部分,并根据你写的东西,它看起来像一个竞争条件。 如果你改变的代码块...

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线程调度。



文章来源: Strange behavior in Task.Run
标签: c# wpf task