与 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 将滞后。所以我需要一个同步方法来访问参数属性,这将等到操作结束。

与 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 问题所示