Task.Factory.StartNew 与 TaskScheduler.Default 冻结 UI。网络4.0
Task.Factory.StartNew with TaskScheduler.Default freezes UI. Net 4.0
我读过一个 Stephen Cleary's article 有必要明确地设置从 TaskScheduler.Current
到 TaskScheduler.Default
。
当我的程序启动时,数据加载就开始了。大约需要 5 秒。如果我在加载数据时单击按钮 添加 ,那么新的 window 将不会打开,直到方法 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
。但是我的示例无法正常工作(加载数据时未打开新 window),因此显示错误行为 I've made a test application and it can be downloaded here.。因为我听到很多关于使用 TaslScheduler.Default
.
的评论
请不要关闭我的线程,因为了解我的 UI 没有响应的原因对我来说非常重要。
Stephen Cleary 的文章非常完美。感谢 Peter Bons 的耐心等待。
构造 Task.Factory.StartNew(() =>}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
完美运行,冻结 UI 的原因是在 UI 线程上执行的另一个操作。
您正在 Dispatcher 上工作......
可能不是你的问题,因为我刚刚在调用
之外看到了你的 thread.sleep
你的问题之一在于这段代码:
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());
另见 Task continuation on UI thread
那么另一个问题是,当您按下添加按钮时,会创建一个 AddPersonVM
的实例。在构造函数中使用此代码:
public AddPersonVM()
{
LoadData();
AddPersonCommand = new RelayCommand<object>(AddPerson);
CancelPersonCommand = new RelayCommand<object>(CancelPerson);
}
当我计算 LoadData()
调用的持续时间时,它需要 1.5 到 4 秒才能完成。该代码必须在调用 ShowDialog()
之前完成。
您不应在构造函数中加载数据。在您的情况下,不是在显示对话框之前。您也可以异步加载该数据,或者您应该打开对话框并使用 Window.Loaded 事件开始获取数据(同步或异步)。因为此时 UI 已经显示,所以您可以在从数据库中获取数据时使用忙碌指示器。
在 MainWindow 中,您有以下代码:
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
未直接显示。
我读过一个 Stephen Cleary's article 有必要明确地设置从 TaskScheduler.Current
到 TaskScheduler.Default
。
当我的程序启动时,数据加载就开始了。大约需要 5 秒。如果我在加载数据时单击按钮 添加 ,那么新的 window 将不会打开,直到方法 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
。但是我的示例无法正常工作(加载数据时未打开新 window),因此显示错误行为 I've made a test application and it can be downloaded here.。因为我听到很多关于使用 TaslScheduler.Default
.
请不要关闭我的线程,因为了解我的 UI 没有响应的原因对我来说非常重要。
Stephen Cleary 的文章非常完美。感谢 Peter Bons 的耐心等待。
构造 Task.Factory.StartNew(() =>}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
完美运行,冻结 UI 的原因是在 UI 线程上执行的另一个操作。
您正在 Dispatcher 上工作......
可能不是你的问题,因为我刚刚在调用
之外看到了你的 thread.sleep你的问题之一在于这段代码:
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());
另见 Task continuation on UI thread
那么另一个问题是,当您按下添加按钮时,会创建一个 AddPersonVM
的实例。在构造函数中使用此代码:
public AddPersonVM()
{
LoadData();
AddPersonCommand = new RelayCommand<object>(AddPerson);
CancelPersonCommand = new RelayCommand<object>(CancelPerson);
}
当我计算 LoadData()
调用的持续时间时,它需要 1.5 到 4 秒才能完成。该代码必须在调用 ShowDialog()
之前完成。
您不应在构造函数中加载数据。在您的情况下,不是在显示对话框之前。您也可以异步加载该数据,或者您应该打开对话框并使用 Window.Loaded 事件开始获取数据(同步或异步)。因为此时 UI 已经显示,所以您可以在从数据库中获取数据时使用忙碌指示器。
在 MainWindow 中,您有以下代码:
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
未直接显示。