C# WPF异步命令阻止用户界面

C# WPF异步命令阻止用户界面,c#,wpf,multithreading,asynchronous,mvvm,C#,Wpf,Multithreading,Asynchronous,Mvvm,我刚开始使用Task.Run()和async,并等待使UI更具响应性,因此我可能没有正确地实现某些功能 我已经阅读了Stephen Cleary关于使用AsyncCommands的优秀文章,并使用了他的代码作为具有响应UI的基础,但当我运行代码时,它似乎仍然冻结(在函数完全完成之前,我无法移动窗口或与其他按钮交互) 我正在尝试执行一个通常需要5-10秒才能返回的搜索。下面是创建AsyncCommand的代码以及函数的作用 代码: public ICommand SearchCommand { 得

我刚开始使用
Task.Run()
和async,并等待使UI更具响应性,因此我可能没有正确地实现某些功能

我已经阅读了Stephen Cleary关于使用AsyncCommands的优秀文章,并使用了他的代码作为具有响应UI的基础,但当我运行代码时,它似乎仍然冻结(在函数完全完成之前,我无法移动窗口或与其他按钮交互)

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

代码:

public ICommand SearchCommand
{
得到
{
如果(_SearchCommand==null)
{
_SearchCommand=AsyncCommand.Create(()=>Search());
}
返回搜索命令;
}
}
专用异步任务搜索()
{
IEnumerable points=await SearchAsync(_CurrentPIServer,NameSearch,PointSourceSearch)。ConfigureAwait(false);
SearchResults.Clear();
foreach(点中的点)
{
SearchResults.Add(point.Name);
}
}
专用异步任务SearchAsync(字符串服务器、字符串名称搜索、字符串点源搜索)
{
{
PIServers KnownServers=新PIServers();
PIServer server=knownserver[server];
Connect();
返回wait Task.Run(()=>PIPoint.FindPIPoints(server,NameSearch,PointSourceSearch)).configurewait(false);
}
}
我认为问题在于我是如何将长时间运行的函数推到一个线程上的,它没有脱离UI线程,或者我对任务和异步/等待是如何完全脱离的理解

编辑1: 根据Stephen的回答,我更新了函数,但我没有看到UI响应方面的任何变化。我创建了第二个命令,执行相同的操作,并且在任何情况下都从UI获得相同的响应。代码现在如下所示

代码:

public ICommand SearchCommand
{
得到
{
如果(_SearchCommand==null)
{
_SearchCommand=AsyncCommand.Create(异步()=>
{
var results=wait Task.Run(()=>Search(_CurrentPIServer,NameSearch,PointSourceSearch));
SearchResults=新的ObservableCollection(results.Select(x=>x.Name));
});
}
返回搜索命令;
}
}
公共ICommand搜索命令2
{
得到
{
如果(_SearchCommand2==null)
{
_SearchCommand2=新的RelayCommand(()=>
{
var results=搜索(_CurrentPIServer,NameSearch,PointSourceSearch);
SearchResults=新的ObservableCollection(results.Select(x=>x.Name));
}
,()=>正确);
}
返回搜索命令2;
}
}
私有IEnumerable搜索(字符串服务器、字符串名称搜索、字符串点源搜索)
{
PIServers KnownServers=新PIServers();
PIServer server=knownserver[server];
Connect();
返回PIPoint.FindPIPoints(服务器、名称搜索、点源搜索);
}
我一定是错过了什么,但我不知道在这一点上是什么

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

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

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

这就是说,有一个
Task.Run
原则需要牢记:将
Task.Run
推到调用堆栈的尽可能高的位置。一般的想法是
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;
  }
}
private IEnumerable搜索(字符串服务器、字符串名称搜索、字符串点源搜索)
{
PIServers KnownServers=新PIServers();
PIServer server=knownserver[server];
//TODO:如果“Connect”或“FindPIPoints”自然是异步的,
//然后该方法应转换回异步方法。
Connect();
返回PIPoint.FindPIPoints(服务器、名称搜索、点源搜索);
}
公共ICommand SearchCommand
{
得到
{
如果(_SearchCommand==null)
{
_SearchCommand=AsyncCommand.Create(异步()=>
{
var results=等待任务。运行(()=>
搜索(_CurrentPIServer,NameSearch,PointSourceSearch));
SearchResults=新的ObservableCollection(
结果:选择(x=>x.Name));
});
}
返回搜索命令;
}
}
您的
服务器是什么
    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);
    }
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;
  }
}