首页 文章

如何实现返回Task <T>的接口方法?

提问于
浏览
30

我有一个界面

interface IFoo
{
  Task<Bar> CreateBarAsync();
}

有两种方法可以创建 Bar ,一种是异步,另一种是同步 . 我想为这两种方法中的每一种提供接口实现 .

对于异步方法,实现可能如下所示:

class Foo1 : IFoo
{
  async Task<Bar> CreateBarAsync()
  {
    return await AsynchronousBarCreatorAsync();
  }
}

但是,我该如何实现使用同步方法创建 Bar 的类 Foo2

我可以实现同步运行的方法:

async Task<Bar> CreateBarAsync()
  {
    return SynchronousBarCreator();
  }

然后,编译器将警告不要在方法签名中使用 async

这种异步方法缺少'等待'运算符并将同步运行 . 考虑使用'await'运算符等待非阻塞API调用,或'await Task.Run(...)'在后台线程上执行CPU绑定工作 .

或者,我可以实现该方法以显式返回 Task<Bar> . 在我看来,代码看起来不那么可读:

Task<Bar> CreateBarAsync()
  {
    return Task.Run(() => SynchronousBarCreator());
  }

从性能的角度来看,我认为这两种方法的开销大致相同,或者?

我应该选择哪种方法;同步实现 async 方法或在 Task 中显式包装同步方法调用?

EDIT

我正在开发的项目实际上是一个.NET 4项目,带有来自Microsoft Async NuGet包的async / await扩展 . 在.NET 4上, Task.Run 可以替换为 TaskEx.Run . 我有意识地在上面的例子中使用了.NET 4.5方法,希望能让主要问题更加清晰 .

4 回答

  • 6

    当您必须从接口实现异步方法并且您的实现是同步的时,您可以使用Ned的解决方案:

    public Task<Bar> CreateBarAsync()
    {
        return Task.FromResult<Bar>(SynchronousBarCreator());
    }
    

    使用此解决方案,该方法看起来是异步但是同步 .

    或者你提出的解决方案:

    Task<Bar> CreateBarAsync()
      {
        return Task.Run(() => SynchronousBarCreator());
      }
    

    这种方法真的是异步的 .

    您没有符合“如何实现返回任务的接口方法”的所有情况的通用解决方案 . 这取决于上下文:你的实现是否足够快,所以在另一个线程上调用它是没用的?如果调用此方法,该界面如何使用?它会冻结应用程序吗?甚至可以在另一个线程中调用您的实现吗?

  • 20

    试试这个:

    class Foo2 : IFoo
    {
        public Task<Bar> CreateBarAsync()
        {
            return Task.FromResult<Bar>(SynchronousBarCreator());
        }
    }
    

    Task.FromResult使用提供的值创建已完成的指定类型的任务 .

  • 8

    如果您使用的是.NET 4.0,则可以使用 TaskCompletionSource<T>

    Task<Bar> CreateBarAsync()
    {
        var tcs = new TaskCompletionSource<Bar>();
        tcs.SetResult(SynchronousBarCreator());
        return tcs.Task
    }
    

    最终,如果你的方法没有异步,你应该考虑暴露一个同步 endpoints ( CreateBar ),它创建一个新的 Bar . 这样就没有惊喜,也没有必要用冗余的 Task 包裹 .

  • 20

    为了补充其他答案,还有一个选项,我相信它也适用于.NET 4.0:

    class Foo2 : IFoo
    {
        public Task<Bar> CreateBarAsync()
        {
            var task = new Task<Bar>(() => SynchronousBarCreator());
            task.RunSynchronously();
            return task;
        }
    }
    

    注意 task.RunSynchronously() . 与 Task<>.FromResultTaskCompletionSource<>.SetResult 相比,它可能是最慢的选项,但有一个微妙而重要的区别: the error propagation behavior .

    上面的方法将模仿 async 方法的行为,其中异常永远不会抛出在同一个堆栈帧上(展开它),而是存储在 Task 对象内的休眠状态 . 调用者实际上必须通过 await tasktask.Result 来观察它,此时它将被重新抛出 .

    Task<>.FromResultTaskCompletionSource<>.SetResult 不是这种情况, SynchronousBarCreator 抛出的任何异常都将直接传播给调用者,展开调用堆栈 .

    我在这里有更详细的解释:

    Any difference between "await Task.Run(); return;" and "return Task.Run()"?

    另外,我建议在设计接口时添加 cancellation 的规定(即使当前未使用/实现取消):

    interface IFoo
    {
        Task<Bar> CreateBarAsync(CancellationToken token);
    }
    
    class Foo2 : IFoo
    {
        public Task<Bar> CreateBarAsync(CancellationToken token)
        {
            var task = new Task<Bar>(() => SynchronousBarCreator(), token);
            task.RunSynchronously();
            return task;
        }
    }
    

相关问题