如何从getter或setter调用异步方法?


223

从C#中的getter或setter调用异步方法的最优雅的方法是什么?

这是一些伪代码来帮助解释自己。

async Task<IEnumerable> MyAsyncMethod()
{
    return await DoSomethingAsync();
}

public IEnumerable MyList
{
    get
    {
         //call MyAsyncMethod() here
    }
}

4
我的问题是为什么。属性应该模仿字段之类的东西,因为它通常应该执行很少(或至少非常快)的工作。如果您有一个长期运行的属性,最好将其作为方法编写,这样调用方就知道它是一个更复杂的工作。
詹姆斯·迈克尔·哈尔

@James:完全正确-我怀疑这就是CTP明确不支持此功能的原因。话虽如此,您始终可以使type的属性Task<T>立即返回,具有正常的属性语义,并且仍然允许根据需要异步处理事物。
Reed Copsey

17
@James我的需要来自使用Mvvm和Silverlight。我希望能够绑定到延迟加载数据的属性。我正在使用的ComboBox扩展类要求绑定发生在InitializeComponent()阶段,但是实际数据加载要晚得多。在尝试使用尽可能少的代码来完成时,getter和async感觉就像是完美的结合。
Doguhan Uluca


詹姆斯(James)和里德(Reed),您似乎忘记了总是有一些极端情况。对于WCF,我想验证放置在属性上的数据是否正确,并且必须使用加密/解密对其进行验证。我用于解密的功能恰好采用了第三方供应商的异步功能。(我无法在这里这样做)。
RashadRivera

Answers:


211

技术上讲async在C#中不允许使用属性。这是一个有目的的设计决策,因为“异步特性”是矛盾的。

属性应返回当前值;他们不应该启动后台操作。

通常,当某人想要“异步属性”时,他们真正想要的是以下之一:

  1. 返回值的异步方法。在这种情况下,请将属性更改为async方法。
  2. 可以在数据绑定中使用的值,但是必须异步计算/检索。在这种情况下,可以async对包含的对象使用工厂方法,也可以使用async InitAsync()方法。数据绑定值将default(T)一直保留到该值被计算/检索。
  3. 该值创建起来很昂贵,但应缓存以备将来使用。在这种情况下,请使用AsyncLazy 我的博客AsyncEx库。这将为您提供await物有所值的财产。

更新:我在最近的“异步OOP”博客文章之一中介绍了异步属性


在第2点中,恕我直言,您没有考虑常规情况,在这种情况下,设置属性应再次初始化基础数据(不仅在构造函数中)。除了使用Nito AsyncEx或使用以外,还有其他方法Dispatcher.CurrentDispatcher.Invoke(new Action(..)吗?
杰拉德

@杰拉德:我不明白为什么在这种情况下第(2)点不起作用。只需实现INotifyPropertyChanged,然后确定是要返回旧值还是default(T)在进行异步更新时。
斯蒂芬·克莱里

1
@Stephan:好的,但是当我在setter中调用async方法时,我收到CS4014“未等待”警告(或者仅在Framework 4.0中才这样)。您是否建议在这种情况下取​​消该警告?
Gerard

@杰拉德:我的第一个建议是使用NotifyTaskCompletionAsyncEx项目中的。或者您可以建立自己的;这并不难。
斯蒂芬·克利西

1
@Stephan:好的,可以尝试一下。也许关于异步数据绑定视图模型场景的一篇不错的文章已经到位。例如,绑定到{Binding PropName.Result}我对我来说并不容易。
杰拉德

101

您不能异步调用它,因为没有异步属性支持,只有异步方法。因此,有两种选择,两种选择都利用了以下事实:CTP中的异步方法实际上只是返回Task<T>或的方法Task

// Make the property return a Task<T>
public Task<IEnumerable> MyList
{
    get
    {
         // Just call the method
         return MyAsyncMethod();
    }
}

要么:

// Make the property blocking
public IEnumerable MyList
{
    get
    {
         // Block via .Result
         return MyAsyncMethod().Result;
    }
}

1
感谢您的答复。选项A:返回任务实际上并不是出于约束目的而进行的锻炼。选项B:如您所提到的,.Result会阻塞UI线程(在Silverlight中),因此需要在后台线程上执行操作。我将看看我是否可以提出一个可行的解决方案。
Doguhan Uluca

3
@duluca:您也可以尝试使用类似以下方法的方法:private async void SetupList() { MyList = await MyAsyncMethod(); } 异步操作完成后,这将导致MyList设置(然后自动绑定,如果它实现了INPC)...
Reed Copsey

该属性必须位于我声明为页面资源的对象中,因此我确实需要此调用源自getter。请为我提出的解决方案查看我的答案。
Doguhan Uluca 2011年

1
@duluca:实际上,我是在建议您这样做。。。但是,请注意,如果您多次快速访问Title,那么您当前的解决方案将导致getTitle()
类似的

很好的一点。尽管对于我的特定情况不是问题,但是对isLoading进行布尔检查可以解决该问题。
Doguhan Uluca

55

由于我的体系结构是分离的,所以我确实需要从get方法发起调用。所以我想出了以下实现。

用法: 标题位于ViewModel或可以静态声明为页面资源的对象中。绑定到它,当getTitle()返回时,将填充该值而不会阻塞UI。

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

9
从2012年8月18日起在Win8 RP中,我们应该将Dispatcher调用更改为: Window.Current.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,async()=> {Title =等待GetTytleAsync(url);});
安东·西齐科夫

7
@ChristopherStevenson,我也这么认为,但我不认为是这种情况。因为该吸气剂被当做一发而忘却的操作,所以在吸气剂完成执行后,如果不完成该调用,则不会更新绑定。
伊恩

3
不,它具有竞争条件,但是由于'RaisePropertyChanged(“ Title”)',用户无法看到它。它在完成之前返回。但是,完成后,您将设置属性。触发PropertyChanged事件。活页夹再次获得财产的价值。
Medeni Baykal 2015年

1
基本上,第一个getter将返回null值,然后将对其进行更新。请注意,如果我们希望每次都调用getTitle,则可能存在一个不良循环。
tofutim '17

1
您还应该知道,异步调用中的任何异常都将被完全吞没。如果有,它们甚至不会到达应用程序上未处理的异常处理程序。
Philter '18 -4-6

9

我认为我们可以等到值先返回null,然后再获取真实值,因此对于Pure MVVM(例如PCL项目),我认为以下是最优雅的解决方案:

private IEnumerable myList;
public IEnumerable MyList
{
  get
    { 
      if(myList == null)
         InitializeMyList();
      return myList;
     }
  set
     {
        myList = value;
        NotifyPropertyChanged();
     }
}

private async void InitializeMyList()
{
   MyList = await AzureService.GetMyList();
}

3
这难道不产生编译器警告CS4014: Async method invocation without an await expression
尼克

6
非常怀疑的跟随此建议。观看此视频,然后做出自己的头脑:channel9.msdn.com/Series/Three-Essential-Tips-for-Async/...
Contango 2015年

1
您应避免使用“异步无效”方法!
SuperJMN

1
每一次喊叫都要附上明智的答复,@ SuperJMN可以向我们解释原因吗?
Juan Pablo Garcia Coello

1
@Contango很好的视频。他说:“ async void仅用于顶级处理程序及其类似对象”。我认为这可能符合“及其相似条件”。
HappyNomad

7

您可以这样使用Task

public int SelectedTab
        {
            get => selected_tab;
            set
            {
                selected_tab = value;

                new Task(async () =>
                {
                    await newTab.ScaleTo(0.8);
                }).Start();
            }
        }

5

我以为.GetAwaiter()。GetResult()正是解决此问题的方法,不是吗?例如:

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            _Title = getTitle().GetAwaiter().GetResult();
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

5
这与只是使用阻止相同.Result-它不是异步的,并且可能导致死锁。
McGuireV10年10

您需要添加IsAsync = True
Alexsandr Ter

感谢您对我的回答的反馈;我真的很希望有人提供这个僵局的示例,以便我可以看到它的实际效果
bc3tech

2

由于您的“异步属性”在视图模型中,因此可以使用AsyncMVVM

class MyViewModel : AsyncBindableBase
{
    public string Title
    {
        get
        {
            return Property.Get(GetTitleAsync);
        }
    }

    private async Task<string> GetTitleAsync()
    {
        //...
    }
}

它将为您处理同步上下文和属性更改通知。


作为财产,必须如此。
德米特里·谢赫特曼

抱歉,但是那时我可能错过了这段代码的重点。你能详细说明一下吗?
帕特里克·霍夫曼

属性根据定义被阻止。没有语法糖,GetTitleAsync()充当“异步获取器”。
德米特里·谢希曼

1
@DmitryShechtman:不,它不必被阻止。这正是更改通知和状态机的用途。而且它们并没有按照定义进行阻止。根据定义,它们是同步的。这与阻止不同。“阻塞”意味着他们可能会做大量的工作,并且可能会花费大量时间来执行。这恰恰是属性不应该执行的操作。
quetzalcoatl '18年

1

死灵法师。
在.NET Core / NetStandard2中,您可以使用Nito.AsyncEx.AsyncContext.Run代替System.Windows.Threading.Dispatcher.InvokeAsync

class AsyncPropertyTest
{

    private static async System.Threading.Tasks.Task<int> GetInt(string text)
    {
        await System.Threading.Tasks.Task.Delay(2000);
        System.Threading.Thread.Sleep(2000);
        return int.Parse(text);
    }


    public static int MyProperty
    {
        get
        {
            int x = 0;

            // /programming/6602244/how-to-call-an-async-method-from-a-getter-or-setter
            // /programming/41748335/net-dispatcher-for-net-core
            // https://github.com/StephenCleary/AsyncEx
            Nito.AsyncEx.AsyncContext.Run(async delegate ()
            {
                x = await GetInt("123");
            });

            return x;
        }
    }


    public static void Test()
    {
        System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
        System.Console.WriteLine(MyProperty);
        System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
    }


}

如果您只是选择System.Threading.Tasks.Task.RunSystem.Threading.Tasks.Task<int>.Run,那么它将不起作用。


-1

我认为下面的示例可能遵循@ Stephen-Cleary的方法,但是我想给出一个编码示例。这用于数据绑定上下文中,例如Xamarin。

该类的构造函数-或实际上是它依赖的另一个属性的setter-可以调用一个异步void,该异步void将在任务完成时填充该属性,而无需等待或阻塞。最终获得值后,它将通过NotifyPropertyChanged机制更新您的UI。

我不确定从构造函数调用aysnc无效的任何副作用。也许评论者会详细说明错误处理等。

class MainPageViewModel : INotifyPropertyChanged
{
    IEnumerable myList;

    public event PropertyChangedEventHandler PropertyChanged;

    public MainPageViewModel()
    {

        MyAsyncMethod()

    }

    public IEnumerable MyList
    {
        set
        {
            if (myList != value)
            {
                myList = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
                }
            }
        }
        get
        {
            return myList;
        }
    }

    async void MyAsyncMethod()
    {
        MyList = await DoSomethingAsync();
    }


}

-1

当我遇到这个问题时,尝试通过setter或构造函数运行异步方法同步使我陷入UI线程的僵局,并且使用事件处理程序需要在常规设计中进行太多更改。
解决方案通常是显式地写我想隐式发生的事情,即让另一个线程处理该操作并让主线程等待其完成:

string someValue=null;
var t = new Thread(() =>someValue = SomeAsyncMethod().Result);
t.Start();
t.Join();

您可能会争辩说我滥用了该框架,但它确实有效。


-1

我查看了所有答案,但是都存在性能问题。

例如:

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

Deployment.Current.Dispatcher.InvokeAsync(async()=> {标题=等待getTitle();});

使用调度程序,这不是一个很好的答案。

但是有一个简单的解决方案,只需执行以下操作:

string _Title;
    public string Title
    {
        get
        {
            if (_Title == null)
            {   
                Task.Run(()=> 
                {
                    _Title = getTitle();
                    RaisePropertyChanged("Title");
                });        
                return;
            }
            return _Title;
        }
        set
        {
            if (value != _Title)
            {
                _Title = value;
                RaisePropertyChanged("Title");
            }
        }
    }

如果您的函数是异步函数,请使用getTitle()。wait()而不是getTitle()
Mahdi Rastegari

-4

您可以将属性更改为 Task<IEnumerable>

并执行以下操作:

get
{
    Task<IEnumerable>.Run(async()=>{
       return await getMyList();
    });
}

并像await MyList一样使用它;

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.