等待异步方法,其中完成由事件处理程序发出信号

4
我正在使用Windows Phone 8中的Contacts对象,在异步方法中调用SearchAsync。SearchAsync需要订阅SearchCompleted事件处理程序,并通过其中一个事件参数传递其结果,这些结果是异步方法所需的(包括调用其他异步方法)。
你如何等待事件的异步完成,即在事件模式和async/await模式之间建立桥梁?
我能想到的唯一解决方案是使用EventWaitHandle,在awaited Task中等待它,类似于以下内容:
using System.Threading;


async Task<string> MyMethod()
{
    string result = null;
    Contacts cons = new Contacts();
    EventWaitHandle handle = new EventWaitHandle(false,EventResetMode.ManualReset);
    cons.SearchCompleted += (sender,args) =>
    {
        // I do all my work on the results of the contact search in this handler
        result = SomeOtherSynchronousOperation(args.Results);

        // When I'm done, I release the thread which was waiting for the results
        handle.Set();
    };
    cons.SearchAsync(String.Empty, FilterKind.None, "My Contact");

    // I can't block this thread (or can I?)
    // So, I launch a task whose sole job is to wait
    await Task.Run(()=>
    {
       // This gets released by the Contacts.SearchCompleted handler when its work is finished,
       // so that MyMethod can finish up and deliver its result
       handle.WaitOne();
    }

    await DoOtherStuffWithResult(result);

    return result;
}

我的实际解决方案(与上面展示的不完全相同)确实可行。虽然上面的代码并不完全代表已实现的解决方案(可能有一两个编译问题),但它应该能够表达概念并阐明我的问题所在。
这让我想知道这是否是唯一的方式,或者接近于最佳实践的方式来等待事件处理程序的执行,如果不是,那么在这里做需要的事情的“最佳实践”是什么。
在异步/等待世界中,Windows同步原语还有用吗?
(基于提供的答案)
这样正确吗?
using Microsoft.Phone.UserData;

string ExtractWhatIWantFromResults(IEnumerable<Contact> results)
{
    string result;

    // Do my processing on the list of contacts, stuff the results into result

    return string;
}

async Task<string> MyMethod()
{
    Contacts cons = new Contacts();
    TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();

    cons.SearchCompleted += (sender,args) =>
    {
        tcs.TrySetResult(ExtractWhatIWantFromResults(args.Results));
    };
    cons.SearchAsync(String.Empty, FilterKind.None, "My Contact");

    return tcs.Task;
}
2个回答

6

TaskCompletionSource 是使用的常见方式。

未经测试(不知道您的类/方法,所以无法进行测试),

Task<string> MyMethodAsync()
{
    Contacts cons = new Contacts();
    TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();

    cons.SearchCompleted += (sender,args) =>
    {
        tcs.TrySetResult(args.Results);
    };
    cons.SearchAsync(String.Empty, FilterKind.None, "My Contact");

    return tcs.Task;
}

@L.B 这个方法不需要使用 async 修饰符 - 只有在该方法使用 await 时才需要。命名返回 Task<T> 的方法为 "Async",例如 MyMethodAsync,也是一个好的习惯。 - Ricibob

3
为了将 EAP 和 TAP 桥接起来,您需要使用 TaskCompletionSource。具体操作如下:

请参考

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

您可以像这样使用它:
async Task<string> MyMethodAsync()
{
  Contacts cons = new Contacts();
  var searchResults = await cons.SearchTaskAsync(String.Empty, FilterKind.None);
  string result = SomeOtherSynchronousOperation(searchResults);
  await DoOtherStuffWithResult(result);
  return result;
}

实际上,TAP的MSDN文档质量非常高,我强烈建议阅读整个部分。
Windows同步原语在异步/等待世界中还有用吗?
不太有用,因为一旦阻塞线程,就会失去异步代码的好处。尽管如此,您可以使用基于TAP的原语模拟类似的行为;Stephen Toub撰写了一系列博客文章来探讨这一点,而我在我的AsyncEx库中实现了类似的原语。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接