【问题标题】:Implement async method synchronously同步实现异步方法
【发布时间】:2017-06-07 10:22:17
【问题描述】:

我有一个接口迫使我实现一个(异步)任务:

namespace Microsoft.Owin.Security.Infrastructure
{
    public interface IAuthenticationTokenProvider
    {
        ..
        Task CreateAsync(AuthenticationTokenCreateContext context);
        ..
    }
}

我想实现这个方法,但我没有等待任何东西。虽然我必须等待或返回一些任务,否则我的代码无法编译。:

public class MyImplementation: IAuthenticationTokenProvider
{
    public async Task CreateAsync(AuthenticationTokenCreateContext context) 
    {
        /// do something synchronously.

        await something here? ??
    }
}

我的问题是:如何在不异步执行某些操作的情况下实现此方法?我已经找到了几个解决方案(等待 task.Yield()await Task.Run(() => {})),但我无法完全理解正确的实现方式和原因。

【问题讨论】:

  • 如果你不等待任何东西,你的代码应该编译得很好——它实际上不会是异步的。如果在您的情况下基本上有一个不执行任何 IO 的快速实现,那可能没问题 - 但否则您应该真正异步地做事......
  • 如果这样做,我会在编译时收到警告:“异步方法缺少 'await' 运算符并将同步运行”。为什么我会收到此警告?为什么重要?
  • 警告不言自明——你有一个异步方法,但它是同步完成的。在你的情况下,这可能是你想要的。如果不知道CreateAsync 方法的主体是做什么的,就很难说清楚。如果它总是很快,那很好,你可以取消那段代码的警告。
  • 让我们看看我是否做对了。就我而言,我想编写以下代码: public Task CreateAsync(AuthenticationTokenCreateContext context) { return Task.FromResult(0); } 因为它不是异步的?
  • 如果您实际上不想做任何异步操作,您可以这样做。但我们不知道同步运行所有代码是否合适,因为我们不知道那段代码是什么。正如 hvd 在 cmets 中提到的那样,这在异常处理方面效果不佳 - 除了 可能 参数验证之外,异步方法通常会返回错误任务而不是抛出异常。

标签: c# asynchronous task


【解决方案1】:

直接实现方法,无需等待:

public async Task<Foo> GetFooAsync()
{
    return new Foo();
}

您不等待任何事情都没关系。异步方法不需要等待任何东西(尽管编译器会警告你你没有等待任何东西)。

如果您不想要 aysnc 方法的开销(与异步“状态机”相关的成本可能是不可取的),您可以

public Task<Foo> GetFooAsync()
{
    return Task.FromResult(new Foo());
}

【讨论】:

  • 不要担心成本(因为这可能是一个微优化),而是担心new Foo() 抛出异常时的效果差异:在async 版本中,返回的任务是表示它没有成功完成。在另一个版本中,根本不会返回任何任务。
  • 问题是我没有使用Task,而只是使用了一个Task。所以这是一个异步无效。因此,创建结果并不是什么。
  • @ClarkKent Task&lt;T&gt; 实现了Task,因此您只需返回Task.FromResult(0) 即可满足Task 的(非通用)返回类型。任务的“价值”不会被使用。
【解决方案2】:

我的解决方案是返回一个Task,它表示已经执行的同步方法的结果。这样,您就可以在异常出现时维护异步方法的语义,而不会产生异步状态机的开销。有趣的是,我实际上是出于同样的原因在同一个界面上这样做的

我有一个扩展方法:

    public static Task AsCompletedTask<T>(this Action<T> func, T arg)
    {
        var tcs = new TaskCompletionSource<VoidResult>();
        try
        {
            func(arg);
            tcs.SetResult(voidResult);
        }
        catch (Exception e)
        {
            tcs.SetException(e);
        };

        return tcs.Task;
    }

然后我通过以下方式调用:

    public Task CreateAsync(AuthenticationTokenCreateContext context)
    {
        return ((Action<AuthenticationTokenCreateContext>)Create).AsCompletedTask(context);
    }

顺便说一句,我讨厌这个名字。如果有人有更好的建议,我会全力以赴。

【讨论】: