【问题标题】:Best way to convert callback-based async method to awaitable task将基于回调的异步方法转换为等待任务的最佳方法
【发布时间】:2014-06-13 05:02:50
【问题描述】:

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

例如,给定以下方法:

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) 的调用封装在任务本身中(即调用本身将在任务内部运行而不是同步运行)

【问题讨论】:

  • 顺便说一句,这不是“经典”的 .net 异步方法。这些是 BeginXxx()EndXxx() 对。另外,您为什么要寻找其他方法来做到这一点?你希望得到什么?
  • 我只是想确保我没有错过一些明显的替代方法来做同样的事情。
  • 这里有什么理由使用TrySetResult而不是SetResult吗?

标签: c# asynchronous task-parallel-library c#-5.0


【解决方案1】:

您的代码简短、可读且高效,所以我不明白您为什么要寻找替代方案,但我想不出任何东西。我认为你的方法是合理的。

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

但如果你只想在Task版本中异步运行(即在ThreadPool上),你可以使用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;
    });
}

【讨论】:

  • 我并不总是控制现有的基于回调的方法,所以如果方法的同步部分执行的时间太长,我想必须选择将其移入任务也是。您的解决方案正好解决了这个问题。
  • 嗯...我假设带有回调的原始 GetStringFromUrl 已经是异步的(因此回调)。如果是这样,这个建议将消耗资源,而只是为了调用调用而从另一个任务中分离出来。
  • @DrewMarsh 即使是异步方法通常也有一些同步部分,尽管大多数时候它可以忽略不计。但是这个问题特别要求,否则我不会提出类似的建议。
  • @svick 当然,通常会有一些启动成本,但除非有人创建了一个非常粗制滥造的实现,否则我会花更多的开销将它扔到另一个线程 Task.Run而不仅仅是让该部分在当前线程上执行。我认为你必须相信你在这方面调用的异步 API,直到你能证明它有罪,我个人绝不建议默认这样做。只是我的 2 美分。
  • 如果这个方法真的是异步的,那么启动一个新任务将失去异步运行作业的所有优点。如果您愿意,您可以在方法调用之前调用 Task.Yield() 将其发布到任务调度程序中。无论如何,问题的代码都会更好。
【解决方案2】:

假设回调只处理成功的情况,您假设的实现对此非常好。如果在 GetStringFromUrl 实现的异步基础中发生异常,当前会发生什么?他们没有真正的方法将其传播到 Action 回调......他们是否只是吞下它并返回 null 或其他东西?

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-01-18
    • 2012-01-04
    • 2018-02-23
    • 1970-01-01
    • 2023-03-22
    • 2018-04-21
    • 1970-01-01
    相关资源
    最近更新 更多