与 UI 中的部分异步
本文关键字:异步 UI | 更新日期: 2023-09-27 18:34:22
我有一个任务,它异步执行,在这个任务的一部分中添加项目在通过Dispatcher.BeginInvoke运行的UI中,我更新了一个ObservebleCollection。对于线程安全访问集合,我使用信号量Slim,但是随着对集合的请求在UI线程中进行,并且Dispatcher.BeginInvoke也可以在UI线程中工作,我收到一个死锁。
private readonly ObservebleCollection<String> parameters = new ObservebleCollection<String>();
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);
//Called from UI
public ObservebleCollection<String> Parameters
{
get
{
semaphore.Wait();
var result = this.parameters;
semaphore.Release();
return result;
}
}
public async Task Operation()
{
await semaphore.WaitAsync();
List<String> stored = new List<String>();
foreach (var parameter in currentRobot.GetParametersProvider().GetParameters())
{
stored.Add(parameter.PropertyName);
}
//Can't do add all items in UI at once, because it's take a long time, and ui started lag
foreach (var model in stored)
{
await UIDispatcher.BeginInvoke(new Action(() =>
{
this.parameters.Add(model);
}), System.Windows.Threading.DispatcherPriority.Background);
}
semaphore.Release();
}
以及我是如何收到死锁的:当我单击程序中的按钮时,操作已执行。当我单击另一个按钮时,程序尝试访问参数属性。我收到了一个死锁=D
问题:在异步操作中,我通过 Dispatcher.BeginInvoke 为每个项目单独填充一个观察集合,因为如果我使用 Dispatcher 一次添加所有项目,UI 将滞后。所以我需要一个同步方法来访问参数属性,这将等到操作结束。
await
确保代码在原始同步上下文(在本例中为UI 线程(中运行后的代码。这使得BeginInvoke
不必要的,因为代码已经在正确的线程上运行。
结果是您尝试从同一线程获取同一对象上的两个锁,从而导致死锁。
如果您希望对对象集合进行线程安全访问,请避免手动创建锁,并使用线程安全集合(如 ConcurrentQueue 或 ConcurrentDictionary(。
除此之外,我不能说我理解代码试图实现的目标,因为它在后台或异步中什么都不做。它很容易成为将参数从一个集合复制到另一个集合的简单方法,如果编写得当,它仍然是线程安全的。你可以写:
var _parameters=new ConcurrentQueue<string>();
....
public void CopyParameters()
{
foreach (var parameter in currentRobot.GetParametersProvider().GetParameters())
{
_parameters.Enqueue(parameter.PropertyName);
}
}
如果对 Parameters 属性使用数据绑定,则只需在添加所有条目后引发PropertyChanged
您要解决的真正问题是什么?
更新
似乎真正的问题是,如果您尝试一次添加太多项目,UI 会冻结。这不是线程问题,而是 WPF 问题。有各种解决方案,所有这些解决方案都涉及仅在完成添加所有属性后提高PropertyChanged
。
只需创建一个包含新值的列表,替换旧值,然后引发 PropertyChanged 事件,例如:
private ObservebleCollection<String> _parameters = new ObservebleCollection<String>();
public ObservebleCollection<String> Parameters
{
get
{
return _parameters;
}
private set
{
_parameters=value;
PropertyChanged("Parameters");
}
public void CopyParameters()
{
var newParameters=currentRobot.GetParametersProvider()
.GetParameters()
.Select(p=>p.PropertyName);
Parameters=new ObservableCollection<string>(newParameters);
}
但是,除非您的代码一次修改Parameters
一项,否则您可以轻松地将ObservableCollection
交换为任何其他集合类型,甚至是string[]
数组。
另一种选择是子类 ObservableCollection 以添加对 AddRange 的支持,如本 SO 问题所示