绑定到Task <t>属性并将IsAsync设置为true

时间:2018-11-24 04:20:44

标签: c# wpf mvvm asynchronous

我正在尝试找到一种将视图绑定到需要使用 <HttpPost()> Function ReturnURL(FullURL As String) As ActionResult Try If FullURL.Contains("ReturnUrl") Then Dim vID As Integer = FullURL.IndexOf("=") Dim vSub As String = FullURL.Substring(vID + 1) Session("ReturnUrl") = vSub.Replace("%2F", "/") End If Return Json("Success") Catch ex As Exception EmailError(ex, 53, PageName) Return Json("Invalid") End Try End Function 方法填充的viewmodel属性的好方法。我最新得到的就是像这样使用IsAsync

ViewModel.cs

async

View.xaml

public class ViewModel
{
    public Task<string> Name { get; set; }

    public ViewModel()
    {
        Name = GetNameAsync();
    }

    public async Task<string> GetNameAsync()
    {
        return await Task.Run(async () =>
        {
            await Task.Delay(5000);
            return "Foo";
        });
    }
}

这似乎可行,但是我不确定是否正在使用<Label Content="{Binding Name.Result, IsAsync=true}" /> ,因为它应该被使用。 documentation没有声明与IsAsync相关的任何内容,还有this comment on a stackoverflowTask与C#IsAsync不相关。

这种实现有什么问题?

2 个答案:

答案 0 :(得分:0)

处理这种“一次性”异步属性的最干净方法是使用Stephen Cleary的Nito.Asyncex库(在Nuget中提供)中的NotifyTaskCompletetion。

您的ViewModel随后将成为

async

然后您将作为标准绑定绑定到public class ViewModel { public INotifyTaskCompletion<string> Name { get; set; } public ViewModel() { Name = NotifyTaskCompletetion.Complete(GetNameAsync()); } private async Task<string> GetNameAsync() { return await Task.Run(async () => { await Task.Delay(5000); return "Foo"; }); } } ,不需要IsAsync。

更多详细信息here

答案 1 :(得分:0)

更正IsAsync选项与Task无关,它只是告诉UI显示回退值,而不会在get调用运行时阻塞。

您的代码可能出什么问题。嗯,我想如果有任何实际错误,我必须进行测试以解决问题。您有很多“坏”的事情。

  1. 构造函数中的代码。您可以保留任务引用,因此,它并不全是坏事,但通常,您不应在构造函数中使用像这样的代码

  2. 在任务上调用.Result。这可能会导致死锁

  3. 具有异步任务,但未等待它。

  4. 使用Task.Run在Task中运行?您可以直接调用await Task.Delay

我将按照以下方式重构您的代码

ViewModel

public class ViewModel : INotifyPropertyChanged
{
    private string name
    public string Name {
        get {return name;}
        set {
               name = value;
               PropertyChanged("Name");
        }
    public ICommand GetNameAsync;

    private void PropertyChanged(string prop)
    {
       if( PropertyChanged != null )
       {
          PropertyChanged(this, new PropertyChangedEventArgs(prop);
       }
    }


    public ViewModel()
    {
         Name="Loading...";
         GetNameAsync =  new AsyncCommand(async () => {Name = await GetNameAsync()});
    }

    private async Task<string> GetNameAsync()
    {
        await nameService.getName();
    }
}

XAML

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

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

    <Label Content="{Binding Name}" />
</Window>

现在,当窗口加载时,您的Xaml将调用GetName函数,标签将显示“ Loading ...”,直到GetName函数完成,然后将其更新为Name

不幸的是

  1. WPF没有AsyncCommand。

    您必须自己制造,或使用第三方框架中的一个(见下文)

  2. INotifyPropertyChanged令人讨厌输入。

    我建议使用Fody及其INotifyPropertyChanged插件 https://github.com/Fody

AsyncCommand

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

using System;  
using System.Threading.Tasks;  
using System.Windows.Input;  

namespace OpenWeather.Command  
{  
    internal class AsyncCommand : ICommand  
    {  
        private readonly Func<Task> _execute;  
        private readonly Func<bool> _canExecute;  
        private bool _isExecuting;  

        public AsyncCommand(Func<Task> execute) : this(execute, () => true)  
        {  
        }  

        public AsyncCommand(Func<Task> execute, Func<bool> canExecute)  
        {  
            _execute = execute;  
            _canExecute = canExecute;  
        }  

        public bool CanExecute(object parameter)  
        {  
            return !(_isExecuting && _canExecute());  
        }  

        public event EventHandler CanExecuteChanged;  

        public async void Execute(object parameter)  
        {  
            _isExecuting = true;  
            OnCanExecuteChanged();  
            try  
            {  
                await _execute();  
            }  
            finally  
            {  
                _isExecuting = false;  
                OnCanExecuteChanged();  
            }  
        }  

        protected virtual void OnCanExecuteChanged()  
        {  
            if (CanExecuteChanged != null) CanExecuteChanged(this, new EventArgs());  
        }  
    }  
}