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() {..}
对于关于下标超出范围错误的部分,您可以创建一个只执行此操作的小型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线程上进行调度。