WPF异步命令阻止UI

时间:2014-08-28 18:57:42

标签: c# wpf multithreading asynchronous mvvm

我是新的使用Task.Run()以及异步并等待使用户界面更具响应性,所以我可能没有正确实现。

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

我正在尝试执行搜索,通常需要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: 按照Stephen的回答我更新了函数,但我没有看到UI响应的任何变化。我创建了第二个执行相同操作的命令,在任何一种情况下我都从UI获得相同的响应。代码现在看起来像下面的

CODE:

    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线程中正确移动工作的主要问题,我只是没有将实际耗时的工作移开。

1 个答案:

答案 0 :(得分:1)

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

另一点值得注意的是,您在ConfigureAwait(false)中使用Search更新SearchResults - 我怀疑这是错误的。如果SearchResults绑定到UI,则在更新时应该处于UI上下文中,因此不应使用ConfigureAwait(false)

那就是说,有一个Task.Run原则值得记住:尽可能推动Task.Run尽可能远的调用堆栈。 I explain this in more detail on my blog。一般的想法是Task.Run应该用于调用同步方法;它不应该用在异步方法的实现中(至少不是那个打算重用的方法)。

最后一点,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;
  }
}