当要调用的基础API是异步时,如何从方法返回值

时间:2013-11-09 13:59:10

标签: c# data-binding windows-phone-8 windows-phone

我有这个场景,在我的Windows Phone App中。

我有一个函数,当提供PhoneNumber时,需要返回联系人的DisplayName。

public string GetDisplayName(string number)
{
    Contacts contactsBook = new Contacts();
    contactsBook.SearchCompleted += contactsBook_SearchCompleted;
    contactsBook.SearchAsync(number, FilterKind.PhoneNumber, null);

    //return .....; How will I be able to return the display name here? 
}

由于SearchAsync返回类型为void,我不能在这里使用await关键字。那我怎么能在这里设计这个功能呢?

这里推荐使用一些线程信令方法的模式吗? (我会在SearchAsync方法之后等待事件,我将在SearchCompleted事件中触发事件)

原因,我想要这样一个功能是因为,我从服务器获取了一组数字,我会在UI中显示相应的名称。

<phone:LongListSelector ItemsSource="{Binding PhoneNumberCollection}">
                    <phone:LongListSelector.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <TextBlock Text="{Binding PhoneNumber, Converter={StaticResource PhoneNumberToNameConverter}}" />
                            </Grid>
                        </DataTemplate>
                    </phone:LongListSelector.ItemTemplate>
                </phone:LongListSelector>

我可以使用下面的IValueConverter转换器

public class PhoneNumberToNameConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ContactsManager.Instance.GetDisplayName((string)value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

1 个答案:

答案 0 :(得分:3)

  

由于SearchAsync返回类型为void,我不能在这里使用await关键字。那我怎么能在这里设计这个功能呢?

SearchAsync返回void,因为它是EAP方法。您可以convert it to a TAP method等待。我更喜欢将这样的TAP包装器转换为扩展方法,如下所示:

public static Task<IEnumerable<Contact>> SearchTaskAsync(this Contacts contacts, string filter, FilterKind filterKind)
{
  var tcs = new TaskCompletionSource<IEnumerable<Contact>>();
  EventHandler<ContactsSearchEventArgs> subscription;
  subscription = (_, args) =>
  {
    contacts.SearchComplete -= subscription;
    try
    {
      tcs.TrySetResult(args.Results);
    }
    catch (Exception ex)
    {
      tcs.TrySetException(ex);
    }
  }
  contacts.SearchComplete += subscription;
  contacts.SearchAsync(filter, filterKind, null);
  return tcs.Task;
}

编写SearchTaskAsync TAP扩展方法后,您可以这样调用它:

private async Task<string> GetDisplayNameAsync(string number)
{
  Contacts contactsBook = new Contacts();
  var matches = await contactsBook.SearchTaskAsync(number, FilterKind.PhoneNumber);
  ...
  return name;
}

但是,这并没有真正解决如何在UI中显示异步数据的问题。答案(如果你考虑的话)就是你当然不能。

相反,您必须设计您的UI,以便它在尚未获得结果时理解(例如,显示微调器),然后在搜索完成时更新它。

请注意,使用值转换器启动是很棘手的。这是有道理的;你真的不希望值转换器开始后台操作! (你可以 hack it to work,但结果不是很好。)

因此,我建议您使用单独的VM属性作为显示名称。您可以使用NotifyTaskCompletion from my AsyncEx library来简化此操作。以下是一个如何运作的例子:

public string Number
{
  get { return _number; }
  set
  {
    _number = value;
    NumberDisplayName = NotifyTaskCompletion.Create(GetDisplayNameAsync(value));
    RaisePropertyChanged("Number");
    RaisePropertyChanged("NumberDisplayName");
  }
}

public INotifyTaskCompletion<string> NumberDisplayName
{
  get; private set;
}

然后您的绑定可以使用NumberDisplayName.Result来显示结果(如果搜索尚未完成,则使用空字符串)。如果你想要一个微调器或诸如此类的东西,你可以使用其他路径,例如NumberDisplayName.IsCompleted;如果你想要正确的错误处理,也有相应的路径(例如,NumberDisplayName.ErrorMessage)。