返回Task的接口的同步实现

时间:2015-01-28 16:53:54

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

Implementing an interface that requires a Task return type in synchronous code类似,虽然我很好奇我是否应该忽略编译错误而不是我的情况。

假设我有这样的界面:

public interface IAmAwesome {
    Task MakeAwesomeAsync();
}

在某些实现中,使用asyncawait异步完成可以带来很多好处。这正是界面试图允许的内容。

在其他情况下,也许很少见,只需要一个简单的同步方法即可实现。所以我们假设实现如下:

public class SimplyAwesome : IAmAwesome {
    public async Task MakeAwesomeAsync() {
        // Some really awesome stuff here...
    }
}

这样可行,但编译器警告:

  

此方法缺少'await'运算符并将同步运行。   考虑使用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!
        });
    }
}

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

3 个答案:

答案 0 :(得分:11)

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

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

答案 1 :(得分:6)

  

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

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

你可以采取三种方法:

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

B中。将“Awesome”的生成分为同步接口和异步接口:

public interface MakeAwesomeAsync
{
    Task MakeAwesomeAsync();
}

public interface MakeAwesome
{
    void MakeAwesome();
}

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

答案 2 :(得分:6)

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

  1. 保留async并忽略警告
  2. 有同步/ async重载
  3. 删除async并返回已完成的任务。
  4. 我建议您返回already completed task

    public class SimplyAwesome : IAmAwesome 
    {
        public Task MakeAwesomeAsync() 
        {
            // run synchronously
            return TaskExtensions.CompletedTask;
        }
    }
    

    有几个原因:

    • 由于编译器添加了try-catch块,因此创建状态机并禁用某些优化(如内联)会使方法async产生轻微的开销。
    • 你需要处理警告,并与任何其他团队成员一起查看此代码,想知道await的位置。
    • 在标记完全同步的方法async时有些不和谐。这就像在代码中添加while(false){}一样,它的行为相同,但它没有传达方法的含义。

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

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

    其次,甚至.Net框架的Task-returns方法也会同步抛出异常。以Task.Delay throws an exception directly without storing it in the returned task为例,因此无需await任务来提出异常:

    try
    {
        Task.Delay(-2);
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
    }
    

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