Task.Factory.StartNew with TaskScheduler.默认冻结UI.Net 4.0
本文关键字:UI Net 冻结 默认 Factory StartNew with TaskScheduler Task | 更新日期: 2023-09-27 18:08:14
我读过Stephen Cleary的一篇文章,有必要显式地设置从TaskScheduler.Current
到TaskScheduler.Default
。
当我的程序启动时,然后开始加载数据。大约需要5秒。如果我在加载数据时点击Add按钮,则新窗口不会打开,直到ReadData()
方法的Task
操作未完成。
ViewModel:
public class MainWindowVM:ViewModelBase
{
public MainWindowVM()
{
AddPersonCommand = new RelayCommand<object>(AddPerson);
ReadData();
}
private void ReadData()
{
PersonData = new ObservableCollection<PersonDepBureau>();
IList<PersonDepBureau> persons;
Task.Factory.StartNew(() =>
{
using (PersonDBEntities db = new PersonDBEntities())
{
persons = (from pers in db.Person
join bu in db.Bureau on pers.Fk_IdBureau equals bu.IdBureau
join dep in db.Departament on pers.Fk_IdDep equals dep.IdDep
select new PersonDepBureau
{
IdPerson = pers.IdPerson,
PersonName = pers.Name,
Surname = pers.Surname,
).ToList();
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
foreach (var person in persons)
{
PersonData.Add(person);
}
}));
}
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
}
//Opening new Window AddPersonWindow.xaml
private void AddPerson(object obj)
{
AddPersonWindow addPersonWindow = new AddPersonWindow();
addPersonWindow.DataContext new AddPersonVM();
addPersonWindow.ShowDialog();
}
}
视图:
<extToolkit:BusyIndicator IsBusy="{Binding IsBusy}" Foreground="Black">
<DataGrid ItemsSource="{Binding PersonData}" SelectedItem="{Binding SelectedPerson}"
IsSynchronizedWithCurrentItem="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding PersonName}"/>
<DataGridTextColumn Header="Surname" Binding="{Binding Surname}" />
</DataGrid.Columns>
</DataGrid>
</extToolkit:BusyIndicator>
<Grid Grid.Row="1">
<Button Command="{Binding AddPersonCommand}" Margin="5" Grid.Column="1">Add</Button>
</Grid>
不能使用async/await
语法糖,因为我的应用程序可以在Windows XP中使用(我不能要求用户安装.NET 4.5
)。谢谢。
这是非常奇怪的行为。我读过的所有信息都是像我一样使用Task
。但是我的例子不能正常工作(加载数据时没有打开新窗口),所以为了显示错误行为,我做了一个测试应用程序,它可以在这里下载。因为我听到了很多使用TaslScheduler.Default
的评论。
请不要关闭我的线程,因为了解我的UI无响应的原因对我来说非常重要。
Stephen Cleary的文章很棒。感谢Peter Bons的耐心。构造Task.Factory.StartNew(() =>}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
是完美的工作,冻结UI的原因是在UI线程上执行的另一个操作
您的问题之一在于以下代码:
Task.Factory.StartNew(() =>
{
using (PersonDBEntities db = new PersonDBEntities())
{
try
{
persons = (from pers in db.Person
join bu in db.Bureau on pers.Fk_IdBureau equals bu.IdBureau
join dep in db.Departament on pers.Fk_IdDep equals dep.IdDep
select new PersonDepBureau
{
IdPerson = pers.IdPerson,
PersonName = pers.Name,
Surname = pers.Surname,
CurrentBureau = bu,
CurrentDepartament = dep
}).ToList();
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
foreach (var person in persons)
{
PersonData.Add(person);
}
}));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
IsBusy = false;
}, CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);
您正在尝试从另一个线程更新UI。这就是为什么要使用调度器。您应该将数据加载从实际(UI)处理逻辑中分离出来。因为你不能使用async/await,你需要使用ContinueWith
类似(伪代码):
var loadDataTask = Task.Factory.StartNew(() =>
{
var persons = new List<PersonDepBureau>();
using (PersonDBEntities db = new PersonDBEntities())
{
persons = (from pers in db.Person
join bu in db.Bureau on pers.Fk_IdBureau equals bu.IdBureau
join dep in db.Departament on pers.Fk_IdDep equals dep.IdDep
select new PersonDepBureau
{
IdPerson = pers.IdPerson,
PersonName = pers.Name,
Surname = pers.Surname,
CurrentBureau = bu,
CurrentDepartament = dep
}).ToList();
}
return persons;
}, CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);
loadDataTask.ContinueWith((t) =>
{
// you can check t.Exception for errors
// Do UI logic here. (On UI thread)
foreach (var person in t.Result)
{
PersonData.Add(person);
}
}, TaskScheduler.FromCurrentSynchronizationContext());
参见UI线程上的任务延续
然后另一个问题是,当您按下添加按钮时,创建了AddPersonVM
的实例。在构造函数中使用以下代码:
public AddPersonVM()
{
LoadData();
AddPersonCommand = new RelayCommand<object>(AddPerson);
CancelPersonCommand = new RelayCommand<object>(CancelPerson);
}
当我计算LoadData()
调用的持续时间时,它需要1.5到4s完成。该代码必须在调用ShowDialog()
之前完成。
不应该在构造函数中加载数据。在你的例子中,不是在对话框显示之前。你既可以异步加载数据,也可以打开对话框并使用Window。加载事件以开始获取数据(同步或异步)。因为此时UI已经显示,所以您可以在从数据库获取数据时使用忙指示符。
在主窗口中你有这样的代码:
private void AddPerson(object obj)
{
AddPersonWindow addPersonWindow = new AddPersonWindow();
AddPersonVM addPersonVM = new AddPersonVM(); // This synchronous code takes about 2 to 5 seconds to complete
addPersonWindow.DataContext = addPersonVM;
addPersonVM.OnRequestClose += (sender, args) => { addPersonWindow.Close(); };
addPersonVM.OnPersonSave += addPersonVM_OnPersonSave;
addPersonWindow.ShowDialog();
}
由于AddPersonVM
的构造函数从数据库加载数据需要大约2到5秒才能完成,因此没有直接显示addPersonWindow
。
您正在Dispatcher上做您的工作.....
可能不是你的问题,因为我刚刚看到了你的帖子。