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

And*_*son 38 .net c# asynchronous task-parallel-library async-await

我有一个界面

interface IFoo
{
  Task<Bar> CreateBarAsync();
}
Run Code Online (Sandbox Code Playgroud)

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

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

class Foo1 : IFoo
{
  async Task<Bar> CreateBarAsync()
  {
    return await AsynchronousBarCreatorAsync();
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  async Task<Bar> CreateBarAsync()
  {
    return SynchronousBarCreator();
  }
Run Code Online (Sandbox Code Playgroud)

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

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

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

  Task<Bar> CreateBarAsync()
  {
    return Task.Run(() => SynchronousBarCreator());
  }
Run Code Online (Sandbox Code Playgroud)

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

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

编辑

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

Ned*_*nov 22

试试这个:

class Foo2 : IFoo
{
    public Task<Bar> CreateBarAsync()
    {
        return Task.FromResult<Bar>(SynchronousBarCreator());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 很高兴我能够提供帮助 (3认同)

Gui*_*ume 22

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

public Task<Bar> CreateBarAsync()
{
    return Task.FromResult<Bar>(SynchronousBarCreator());
}
Run Code Online (Sandbox Code Playgroud)

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

或者你提出的解决方案:

  Task<Bar> CreateBarAsync()
  {
    return Task.Run(() => SynchronousBarCreator());
  }
Run Code Online (Sandbox Code Playgroud)

这种方法真的是异步的.

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

  • 你不应该使用[*async over sync*](http://blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx) (13认同)
  • `Task.Run`几乎总是被过度使用.确实只有一个原因可以使用它,那就是在GUI应用程序上进行计算时.但它很少有计算(相对于CPU)密集型任务. (2认同)
  • @Aron 我同意它经常被过度使用。当您必须调用同步的第三方库时,即使它执行本可以异步完成的耗时任务,它也很有用。 (2认同)

Yuv*_*kov 8

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

Task<Bar> CreateBarAsync()
{
    var tcs = new TaskCompletionSource<Bar>();
    tcs.SetResult(SynchronousBarCreator());
    return tcs.Task
}
Run Code Online (Sandbox Code Playgroud)

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

  • 是的,您可以使用它而无需任何扩展.`Task.FromResult`和`TaskEx.FromResult`只是`TaskCompletionSource'的包装器. (3认同)

nos*_*tio 6

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

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

注意task.RunSynchronously().与和相比,它可能是最慢的选项,但是存在一个微妙但重要的区别:错误传播行为.Task<>.FromResultTaskCompletionSource<>.SetResult

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

这是不符合的情况下Task<>.FromResultTaskCompletionSource<>.SetResult,其中通过抛出的任何异常SynchronousBarCreator将直接传递到调用者,展开调用堆栈.

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

"await Task.Run(); 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;
    }
}
Run Code Online (Sandbox Code Playgroud)