【问题标题】:WPF async command blocking UIWPF 异步命令阻止 UI
【发布时间】:2014-08-28 18:57:42
【问题描述】:

我是新人,使用 Task.Run() 以及 async 和 await 以使 UI 响应更快,所以我可能没有正确实现某些东西。

我已经阅读了 Stephen Cleary 关于使用 AsyncCommands 的精彩文章,并使用了他来自 Patterns for Asynchronous MVVM Applications: Commands 的代码作为拥有响应式 UI 的基础,但是当我运行代码时,它似乎仍然冻结(我无法移动窗口或与其他按钮交互,直到功能完全完成。

我正在尝试执行通常需要 5-10 秒才能返回的搜索。下面是创建AsyncCommand 的代码以及函数的作用。

代码:

    public ICommand SearchCommand
    {
        get
        {
            if (_SearchCommand == null)
            {
                _SearchCommand = AsyncCommand.Create(() => Search());
            }
            return _SearchCommand;
        }
    }
    private async Task Search()
    {
        IEnumerable<PIPoint> points = await SearchAsync(_CurrentPIServer, NameSearch, PointSourceSearch).ConfigureAwait(false);
        SearchResults.Clear();
        foreach (PIPoint point in points)
        {
            SearchResults.Add(point.Name);
        }
    }
    private async Task<IEnumerable<PIPoint>> SearchAsync(string Server, string NameSearch, string PointSourceSearch)
    {
        {
            PIServers KnownServers = new PIServers();
            PIServer server = KnownServers[Server];
            server.Connect();
            return await Task.Run<IEnumerable<PIPoint>>(()=>PIPoint.FindPIPoints(server, NameSearch, PointSourceSearch)).ConfigureAwait(false);
        }
    }

我认为问题在于我如何将长时间运行的函数推送到线程上,并且它没有脱离 UI 线程,或者我对 Tasks 和 async/await 如何完全关闭的理解。

编辑 1: 按照斯蒂芬的回答,我更新了功能,但我没有看到 UI 响应能力有任何变化。我创建了第二个命令来执行相同的操作,并且在任何一种情况下我都会从 UI 中得到相同的响应。现在的代码如下所示

代码:

    public ICommand SearchCommand
    {
        get
        {
            if (_SearchCommand == null)
            {
                _SearchCommand = AsyncCommand.Create(async () =>
                {
                    var results = await Task.Run(()=>Search(_CurrentPIServer, NameSearch, PointSourceSearch));
                    SearchResults = new ObservableCollection<string>(results.Select(x => x.Name));
                });
            }
            return _SearchCommand;
        }
    }
    public ICommand SearchCommand2
    {
        get
        {
            if (_SearchCommand2 == null)
            {
                _SearchCommand2 = new RelayCommand(() =>
                {
                    var results = Search(_CurrentPIServer, NameSearch, PointSourceSearch);
                    SearchResults = new ObservableCollection<string>(results.Select(x => x.Name));
                }
                ,()=> true);
            }
            return _SearchCommand2;
        }
    }
    private IEnumerable<PIPoint> Search(string Server, string NameSearch, string PointSourceSearch)
    {
        PIServers KnownServers = new PIServers();
        PIServer server = KnownServers[Server];
        server.Connect();
        return PIPoint.FindPIPoints(server, NameSearch, PointSourceSearch);
    }

我一定错过了什么,但我现在不确定是什么。

编辑 2: 在对花费了这么长时间的原因进行了更多调查之后,发现结果后对列表的迭代是挂起该过程的原因。通过简单地更改 Search 函数返回的内容并让它已经在对象列表上迭代,可以让 UI 保持响应。我将斯蒂芬的回答标记为正确,因为它处理了我的主要问题,即正确地将工作移出 UI 线程,我只是没有移走实际耗时的工作。

【问题讨论】:

  • 你的server.Connect()方法在做什么?
  • @yuval-itzchakov 说对象和方法是由第三方提供的,但是在调试中它是一个快速的操作。 .FindPIPoints 方法是阻止程序响应的缓慢操作。
  • 除了这个方法还有其他运行吗?任何长时间运行的 UI 操作?
  • @YuvalItzchakov 这是唯一运行的方法。单击调用命令之前的整个程序只是在等待用户。一旦用户单击按钮,我只希望 UI 在等待响应时能够响应。

标签: c# wpf multithreading asynchronous mvvm


【解决方案1】:

我的第一个猜测是排队到Task.Run的工作相当快,延迟是由其他代码引起的(例如PIServer.Connect)。

另一件值得注意的事情是,您在Search 中使用了ConfigureAwait(false),它更新了SearchResults——我怀疑这是错误的。如果 SearchResults 绑定到 UI,那么更新时应该在 UI 上下文中,因此不应使用 ConfigureAwait(false)

也就是说,有一个Task.Run 原则需要牢记:将Task.Run 尽可能推到调用堆栈上。 I explain this in more detail on my blog。总体思路是Task.Run应该用于invoke同步方法;它不应该在异步方法的实现中使用(至少,不应该被重用)。

最后一点,async 本质上是功能性的。因此,返回结果比作为副作用更新集合更自然。

结合这些建议,生成的代码如下所示:

private IEnumerable<PIPoint> Search(string Server, string NameSearch, string PointSourceSearch)
{
  PIServers KnownServers = new PIServers();
  PIServer server = KnownServers[Server];
  // TODO: If "Connect" or "FindPIPoints" are naturally asynchronous,
  //  then this method should be converted back to an asynchronous method.
  server.Connect();
  return PIPoint.FindPIPoints(server, NameSearch, PointSourceSearch);
}

public ICommand SearchCommand
{
  get
  {
    if (_SearchCommand == null)
    {
      _SearchCommand = AsyncCommand.Create(async () =>
      {
        var results = await Task.Run(() =>
            Search(_CurrentPIServer, NameSearch, PointSourceSearch));
        SearchResults = new ObservableCollection<string>(
            results.Select(x => x.Name));
      });
    }
    return _SearchCommand;
  }
}

【讨论】:

  • 感谢您如此迅速地回复。我尝试使用上面的代码,稍微更改()=&gt;Search(。在逐步调试PIServer.Connect() 时速度很快,而PIPoint.FindPIPoints 速度很慢,这就是我试图将其移至另一个线程的原因。在任何情况下,窗口在执行期间仍然冻结。是否有一些事情我应该查看以使其响应?
  • 你确定不是别的吗?使用此代码,所有内容都通过Task.Run 卸载,除了复制集合,这应该非常快。
猜你喜欢
  • 2010-09-14
  • 1970-01-01
  • 2016-02-15
  • 1970-01-01
  • 2011-02-07
  • 2014-01-20
  • 1970-01-01
  • 2013-02-18
  • 2016-05-22
相关资源
最近更新 更多