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

时间:2014-11-05 07:14:11

标签: c# .net asynchronous task-parallel-library async-await

我有一个界面

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

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

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

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

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

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

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

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

  

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

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

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

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

我应该选择哪种方法;同步实现async方法或在Task

中显式包装同步方法调用

修改

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

4 个答案:

答案 0 :(得分:21)

试试这个:

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

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

答案 1 :(得分:21)

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

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

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

或者您提出的解决方案:

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

这种方法真的是异步。

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

答案 2 :(得分:8)

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

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

最终,如果你的方法没有任何异步,你应该考虑暴露一个创建新CreateBar的同步端点(Bar)。这样就没有任何意外,也没有必要用冗余的Task包装。

答案 3 :(得分:6)

为了补充其他答案,还有一个选项,我相信它也适用于.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相比,可能是最慢的选项,但有一个微妙但重要的区别:错误传播行为

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

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

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

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

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

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;
    }
}