返回Task的接口的同步实现

Yuc*_*uck 8 c# multithreading asynchronous task-parallel-library async-await

类似于在同步代码中实现需要Task返回类型的接口,尽管我很好奇我是否应该忽略编译器错误而不是我的情况.

假设我有这样的界面:

public interface IAmAwesome {
    Task MakeAwesomeAsync();
}
Run Code Online (Sandbox Code Playgroud)

在一些实现中,通过使用async和异步完成来实现令人敬畏的好处await.这实际上是接口试图允许的内容.

在其他情况下,也许很少见,只需要一个简单的同步方法来制作真棒.所以我们假设实现如下:

public class SimplyAwesome : IAmAwesome {
    public async Task MakeAwesomeAsync() {
        // Some really awesome stuff here...
    }
}
Run Code Online (Sandbox Code Playgroud)

这有效,但编译器警告:

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

编译器实际上是在建议这个解决方案:

public class SimplyAwesome : IAmAwesome {
    public async Task MakeAwesomeAsync() {
        await Task.Run(() => {
            // Some really awesome stuff here, but on a BACKGROUND THREAD! WOW!
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是 - 当我选择忽略此编译器警告时应该确定什么?在某些情况下,工作非常简单,为它产生一个线程无疑会适得其反.

Ser*_*rvy 13

如果你确实想要同步进行工作,你知道你的async方法将始终同步运行,并且在这种情况下是理想的,然后一定要忽略警告.如果您了解警告告诉您的内容并感觉它所描述的操作是正确的,那么这不是问题.这是一个警告而不是错误的原因.

当然,另一种选择是不要制作方法async,而只是简单地Task.FromResult用来返回已经完成的任务.它会改变错误处理语义(除非你还捕获所有异常并将它们包装到你返回的任务中),所以至少要注意这一点.如果您确实希望通过结果传播异常Task,则可能值得保留该方法async并禁止警告.


Yuv*_*kov 7

当我选择忽略此编译器警告时应该确定什么?在某些情况下,工作非常简单,为它产生一个线程无疑会适得其反.

编译器并没有说"在此方法中使用Task.Run".它只是告诉你你准备了一个async方法,将async修饰符添加到你的方法声明中,但你实际上并没有等待任何东西.

你可以采取三种方法:

答:你可以忽略编译器警告,一切都会执行.请注意,状态机生成会有轻微的开销,但您的方法调用将同步执行.如果操作耗时并且可能导致方法执行进行阻塞调用,则这可能会使使用此方法的用户感到困惑.

B.将"Awesome"的生成分为同步接口和异步接口:

public interface MakeAwesomeAsync
{
    Task MakeAwesomeAsync();
}

public interface MakeAwesome
{
    void MakeAwesome();
}
Run Code Online (Sandbox Code Playgroud)

C.如果操作不太耗时,您可以简单地将其包装在Task使用中Task.FromResult.我肯定会在选择之前测量运行CPU绑定操作所需的时间.


i3a*_*non 7

正如其他人已经指出的那样,你有3种不同的选择,所以这是一个观点问题:

  1. 保持它async并忽略警告
  2. 有同步/ async重载
  3. 删除async并返回已完成的任务.

我建议您返回已完成的任务:

public class SimplyAwesome : IAmAwesome 
{
    public Task MakeAwesomeAsync() 
    {
        // run synchronously
        return TaskExtensions.CompletedTask;
    }
}
Run Code Online (Sandbox Code Playgroud)

有几个原因:

  • 由于编译器添加了try-catch块,因此创建一个方法async会有一点创建状态机和禁用某些优化(如内联)的开销.
  • 您需要处理警告,并与任何其他团队成员一起查看此代码,想知道它在哪里await.
  • 标记完全同步的方法有点不协调async.这就像添加while(false){}你的代码一样,它会起到同样的作用,但它并没有传达方法的含义.

Servy指出,返回任务会改变异常处理的语义.虽然这是真的,但我认为这不是问题.

首先,大多数async代码调用一个方法并在同一个地方等待返回的任务(即await MakeAwesomeAsync()),这意味着无论方法是否存在,异常都将被抛出到同一个地方async.

其次,甚至.Net框架的Task-returns方法也会同步抛出异常.例如Task.Delay,直接抛出异常而不将其存储在返回的任务中,因此不需要await任务来引发异常:

try
{
    Task.Delay(-2);
}
catch (Exception e)
{
    Console.WriteLine(e);
}
Run Code Online (Sandbox Code Playgroud)

由于.Net开发人员需要除了在.Net中同步遇到异常外,除了代码之外,它们也应该是合理的.