【问题标题】:Async with part in UI与 UI 中的部分异步
【发布时间】:2014-12-15 17:11:36
【问题描述】:

我有一个异步执行的任务,在此任务的一部分中,通过 Dispatcher.BeginInvoke 在 UI 中添加项目,我在其中更新了 ObservebleCollection。对于对集合的线程安全访问,我使用了 semaphoreSlim,但是随着对 Collection 的请求在 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 分别为每个项目填充一个 observablecollection,因为如果我使用 Dispatcher 一次添加所有项目,UI 将滞后。所以我需要一个同步方法来访问参数属性,它会等到操作结束。

【问题讨论】:

  • await 确保代码将在正确的线程上运行,那么使用 BeginInvoke 有什么意义呢?另外,当所有修改都发生在同一个方法中,在同一个线程中,你为什么要尝试锁定Parameters
  • 我无法理解您的代码。您可以用简单的英语更好地解释您要达到的目标。这样会好很多。
  • 也不能说我理解代码的作用。我没有看到任何使用异步操作的地方,也没有在后台发生任何事情。您在这里试图解决的真正问题是什么?
  • 我使用 Dispatcher 以后台优先级添加项目,因为如果一次添加所有项目,ui 会滞后。
  • 你为什么要这样做?为什么你需要使用Semaphore?您正在更新 UI 元素,它们只能从 UI 线程访问。

标签: c# wpf asynchronous


【解决方案1】:

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 的支持,如 this SO question 所示

【讨论】:

  • 问题:在异步操作中,我通过 Dispatcher.BeginInvoke 分别为每个项目填充一个 observablecollection,因为如果我使用 Dispatcher 一次添加所有项目,UI 将滞后。
  • 所以您的真正问题是您向集合中添加了很多项目,而 UI 更新速度不够快。这是一个WPF问题。对此的解决方案是使用 ObservableCollection,但在添加所有项目后引发 NotifyPropertyChanged 事件。或者,您可以使用。您尝试执行的操作仍会在 UI 线程上运行,从而导致相同的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-28
  • 1970-01-01
  • 2014-01-30
  • 1970-01-01
相关资源
最近更新 更多