wpf 如何在视图模型中调用异步方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39456542/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:55:24  来源:igfitidea点击:

How to call an async method in viewmodel

c#wpfasynchronousmvvm

提问by r9s

I'm trying to learn MVVM pattern in WPF application. I wrote this async method in my viewmodel (it has to be async since I'm using HttpClient and it's methods are async):

我正在尝试在 WPF 应用程序中学习 MVVM 模式。我在我的视图模型中编写了这个异步方法(它必须是异步的,因为我使用的是 HttpClient 并且它的方法是异步的):

public async Task<Dictionary<int, BusStop>> GetBusStops()
    {
        var busStopDict = new Dictionary<int, BusStop>();
        var url = "my url";

        using (HttpClient client = new HttpClient())
        using (HttpResponseMessage response = await client.GetAsync(url))
        using (HttpContent content = response.Content)
        {
            string data = await content.ReadAsStringAsync();
            var regularExpression = Regex.Match(data, "\[(.)*\]");
            var result = regularExpression.Groups[0];

            var json = JValue.Parse(result.ToString());
            var jsonArray = json.ToArray();

            foreach (var a in jsonArray)
            {
                // irrelevant logic

                busStopDict.Add(nr, bs);
            }
        }

        return busStopDict;
    }

This methods returns a dictionary filled with bus stops (my model). I would like to bind this dictionary with combobox in view, but I can't get it work, because I can't call this async method in constructor of my viewmodel and I have no idea where can I call it. Do you have any suggestions?

这个方法返回一个充满公交车站的字典(我的模型)。我想在视图中将这个字典与组合框绑定,但我无法让它工作,因为我无法在我的视图模型的构造函数中调用这个异步方法,我不知道我可以在哪里调用它。你有什么建议吗?

回答by NtFreX

I would not recommend to write logic in your viewmodel constructor. Instead I would create an Loaded event trigger in your view also to ensure you do not interfere with the loading procedure of the view.

我不建议在您的视图模型构造函数中编写逻辑。相反,我会在您的视图中创建一个 Loaded 事件触发器,以确保您不会干扰视图的加载过程。

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"


<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding LoadedCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

Then in your viewmodel I recomend doing the following:

然后在您的视图模型中,我建议执行以下操作:

Add the following property for your Loaded event

为您的 Loaded 事件添加以下属性

public DelegateCommand LoadedCommand { get; }

Then assign it in your constructor

然后在您的构造函数中分配它

LoadedCommand = new DelegateCommand(async () => await ExecuteLoadedCommandAsync());

Add the loaded method and call your method within it

添加加载的方法并在其中调用您的方法

private async Task ExecuteLoadedCommandAsync()
{
    var busStops = await GetBusStops();
    //TODO: display the busStops or do something else
}

Furthermore is adding "Async" as suffix to your asynchron methods names a good naming pattern. It enables you to quickly see which methods are asynchron. (so rename "GetBusStops" to "GetBusStopsAsync")

此外,将“Async”作为后缀添加到您的异步方法名称是一个很好的命名模式。它使您能够快速查看哪些方法是异步的。(因此将“GetBusStops”重命名为“GetBusStopsAsync”)

This is a simple DelegateCommandimplementation

这是一个简单的DelegateCommand实现

public class DelegateCommand : ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;

    public event EventHandler CanExecuteChanged;

    public DelegateCommand(Action<object> execute) 
               : this(execute, null)
    {
    }

    public DelegateCommand(Action<object> execute, 
               Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public override bool CanExecute(object parameter)
    {
        if (_canExecute == null)
        {
            return true;
        }

        return _canExecute(parameter);
    }

    public override void Execute(object parameter)
    {
        _execute(parameter);
    }

    public void RaiseCanExecuteChanged()
    {
        if( CanExecuteChanged != null )
        {
            CanExecuteChanged(this, EventArgs.Empty);
        }
    }
}

When using this implementation you need to change your initialising of the DelegateCommandin your viewmodel constructor to the following

使用此实现时,您需要将DelegateCommand视图模型构造函数中的初始化更改为以下内容

LoadedCommand = new DelegateCommand(async (param) => await ExecuteLoadedCommandAsync());

回答by Stephen Cleary

You should use asynchronous data binding(I have an entire article on the subject).

您应该使用异步数据绑定(我有一篇关于该主题的整篇文章)。

Using NotifyTaskfrom my Mvvm.Async library, it could look like this:

使用NotifyTask我Mvvm.Async库,它可能是这样的:

public async Task<Dictionary<int, BusStop>> GetBusStopsAsync() { ... }
public NotifyTask<Dictionary<int, BusStop>> BusStops { get; }

MyViewModelConstructor()
{
  BusStops = NotifyTask.Create(() => GetBusStopsAsync());
}

Then your view can model-bind to BusStops.Resultto get the dictionary (or null if it isn't retrieved yet), and also data-bind to BusStops.IsNotCompleted/BusStops.IsFaultedfor busy spinners / error indicators.

然后您的视图可以模型绑定到BusStops.Result以获取字典(如果尚未检索,则为 null),并且还可以将数据绑定到BusStops.IsNotCompleted/BusStops.IsFaulted用于繁忙的微调器/错误指示器。

回答by Fruchtzwerg

Start your async method in the constructor and define an action to be continued with like.

在构造函数中启动异步方法并定义要继续的操作。

//Constructor
public ViewModel()
{
    GetBusStops().ContinueWith((BusStops) =>
    {
        //This anonym method is called async after you got the BusStops
        //Do what ever you need with the BusStops
    });
}

Don′t forget to invoke the UI thread if you want to access an property used for the View with

如果您想访问用于 View 的属性,请不要忘记调用 UI 线程

Application.Current.Dispatcher.BeginInvoke(() =>
{
    //Your code here
});

回答by Kevin B Burns

I would check out AsycLazyor check out AsyncCommandsand create a async Task based "LoadCommand". You shouldn't put much logic into a contructor as it will make it tougher to debug, forces you to strongly couple and will make it very difficult to write Unit tests for your view Model. I tend to make everything lazy if I can.

我会查看AsycLazy或查看AsyncCommands并创建一个基于“LoadCommand”的异步任务。您不应该在构造函数中放入太多逻辑,因为它会使调试变得更加困难,迫使您进行强耦合,并使为您的视图模型编写单元测试变得非常困难。如果可以的话,我倾向于让一切变得懒惰。

AsyncLazy
http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html

AsyncLazy
http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html

AsyncCommand
http://mike-ward.net/2013/08/09/asynccommand-implementation-in-wpf/

AsyncCommand
http://mike-ward.net/2013/08/09/asynccommand-implementation-in-wpf/