将基于回调的异步方法转换为可等待任务的最佳方法


77

转换/包装使用回调的“经典”异步方法的最佳方法是什么,该方法返回一个(可等待的)任务?

例如,给定以下方法:

public void GetStringFromUrl(string url, Action<string> onCompleted);

我知道将其包装到返回任务的方法中的唯一方法是:

public Task<string> GetStringFromUrl(string url)
{
     var t = new TaskCompletionSource<string>();

     GetStringFromUrl(url, s => t.TrySetResult(s));

     return t.Task;
}

这是完成此任务的唯一方法吗?

并且有一种方法可以将对GetStringFromUrl(url,callback)的调用包装在任务本身中(即,调用本身将在任务内部运行而不是同步运行)


2
顺便说一句,这不是“经典” .net异步方法。那些是BeginXxx()EndXxx()成对的。另外,您为什么要寻找其他方法来做到这一点?您希望获得什么?
svick审核

7
我只是想确保我不会错过一些明显的替代方法来做同样的事情。
菲利普·莱巴特

有什么理由TrySetResult代替SetResult这里吗?
本·胡伯

Answers:


41

您的代码简短,易读且高效,因此我不明白您为什么要寻找替代方案,但我什么也没想到。我认为您的方法是合理的。

我也不确定为什么您认为同步部分在原始版本中还可以,但是您想在Task基于-的部分中避免它。如果您认为同步部分可能花费太长时间,请针对两种方法版本都将其修复。

但是,如果您只想ThreadPool在该Task版本中异步运行(即在上),则可以使用Task.Run()

public Task<string> GetStringFromUrl(string url)
{
    return Task.Run(() =>
    {
        var t = new TaskCompletionSource<string>();

        GetStringFromUrl(url, s => t.TrySetResult(s));

        return t.Task;
    });
}

1
我并不总是控制现有的基于回调的方法,因此,如果该方法的同步部分执行的操作花费的时间太长,那么我也必须选择在任务中移动它。您的解决方案可以解决此问题。
菲利普·莱伯特

1
嗯...我以为带有回调的原始GetStringFromUrl已经是异步的(因此回调)。如果是这样,则此建议将消耗资源来分解另一个任务以仅调用该调用。
德鲁·马什

@DrewMarsh即使异步方法通常也具有一些同步部分,尽管在大多数情况下它可以忽略不计。但是这个问题专门要求这样做,否则我不会提出任何类似的建议。
svick 2012年

1
@svick当然,通常会有一些启动成本,但是除非有人创建了一个真正的拙劣实现,否则我将一无所获,这将使您花费更多的开销将其扔到Task.Run的另一个线程上,而不仅仅是在部件上执行。当前线程。我认为您在这方面一定要信任您正在调用的异步API,直到您可以证明它有罪为止,而且我个人不建议您默认使用它。只是我的2美分。
德鲁·马什

如果此方法确实是异步的,则开始新任务将失去异步运行作业的所有优点。如果需要,可以在方法调用之前调用Task.Yield()将其发布到任务计划程序中。Question的代码无论如何都会更好。
Alex Lyalka,

5

对于此假设,假设回调仅处理成功的情况,那么您假定的实现就很好。如果GetStringFromUrl实现的异步基础内发生异常,当前会发生什么?他们没有真正的方式将其传播到Action回调...他们只是吞下它并返回null还是什么?

我唯一推荐的方法是使用遵循新约定的XXXAsync后缀命名此类异步方法。

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.