在已经等待的任务上调用.Result是否会破坏异步模式?

时间:2019-06-12 19:17:45

标签: c# .net .net-core async-await

在代码调用Task.Result时,它已经在等待,所以这里的异步模式仍然成立吗?

class Program
{
    static async Task Main(string[] args)
    {
        var addNumbersTask = AddNumbers(10, 20);

        var result = AwaitForResult(addNumbersTask).Result;

        Console.WriteLine(result);
    }

    static async Task<int> AddNumbers(int a, int b)
    {
        await Task.Delay(250);
        return a + b;
    }

    static async Task<int> AwaitForResult(Task<int> task)
    {
        await task;

        return task.Result;
    }
}

如果您感兴趣的话,请尝试:尝试为需要处理异步调用的代理类发出IL代码,但是我不想在IL中生成异步状态机。因此,我认为可以将实际的“等待”部分委派给IL之外的帮助者。另外,我知道那里有代理类型,但我内无望的工程师想自己写。

编辑:更新示例。

interface IService
{
    Task<int> AddAsync(int a, int b);
}

class Service : IService
{
    public async Task<int> AddAsync(int a, int b)
    {
        await Task.Delay(250);  // Some web service call...
        return a + b;
    }
}

// This class 100% generated via reflection emit
class Proxy : IService
{
    private readonly IService _actual;

    public Proxy(IService actual) => _actual = actual;

    public Task<int> AddAsync(int a, int b)
    {
        return Awaiter.Await(_actual.AddAsync(a, b));
    }
}

static class Awaiter
{
    public static async Task<int> Await(Task<int> task)
    {
        return await task;
    }
}

class Program
{

    static async Task Main(string[] args)
    {
        var proxy = new Proxy(new Service());
        var result = await proxy.AddAsync(5, 5);

        Console.WriteLine($"Result is {result}", result);
    }
}

3 个答案:

答案 0 :(得分:1)

  

这里的异步模式仍然成立吗?

不。在任务上使用await并没有什么神奇的。相反,请考虑任务是否完成。调用task.Result将阻塞调用线程,直到task完成。 await task会异步等待,直到task完成。

因此在此代码中,Result将不会阻止:

static async Task<int> AwaitForResult(Task<int> task)
{
  // Task may not yet be complete here.
  await task;
  // At this point, task is complete.

  // Since task is complete, Result does not block.
  return task.Result;
}

但这与这段代码完全不同:

var result = AwaitForResult(addNumbersTask).Result;

// Equivalent to:
var task = AwaitForResult(addNumbersTask);
var result = task.Result;

AwaitForResult返回的任务在这里可能未完成,因为它从未被await处理。如果未完成,则Result将阻止。

  

试图为需要处理异步调用的代理类发出IL代码,但是我不想在IL中生成异步状态机。因此,我认为可以将实际的“等待”部分委派给IL之外的帮助者。

您是否尝试过Roslyn API?我发现它们比IL发射要方便得多。

如果您的代理服务器只是一个传递对象,那么您可以直接返回内部任务:

// This class 100% generated via reflection emit
class Proxy : IService
{
  private readonly IService _actual;

  public Proxy(IService actual) => _actual = actual;

  public Task<int> AddAsync(int a, int b) => _actual.AddAsync(a, b);
}

但是,如果您想添加很多实际的逻辑,那么我建议使用Roslyn为您生成async状态机。

答案 1 :(得分:0)

如果您想避免屏蔽,则不会。

在您的控制台应用程序示例中,它几乎没有什么区别,但是如果您在不想阻止的地方这样做-例如UI事件处理程序-.Result仍会阻止。

等到addNumbersTask中有一个值时,该任务就会启动。然后,您立即将任务传递给AwaitForResult,后者立即开始等待它,此时返回int incomplete 任务将返回到main() ,然后在其上调用.Result。现在,您将在250ms的大部分时间内一直阻塞该线程,直到该任务将其int结果返回到.Result中的main()

您在问题“等待中”的表达方式不等于“完成”。

答案 2 :(得分:0)

方法Awaiter.Await基本没有意义。除了增加一些开销外,返回的任务在功能上与传入的任务相同。

简单地返回_actual.AddAsync(a, b)的结果和等待它之间的唯一实际区别是,如果AddAsync引发异常,如果方法为async,它将返回错误任务而不是抛出。因此,如果无法执行该操作,或者如果Proxy.AddAsync可以在这种情况下安全地抛出自身,则只需返回该值,而无需执行任何操作。如果您需要确保Proxy.AddAsync从不抛出异常,而如果Proxy.AddAsync抛出异常,则返回错误的任务,那么您只需要将方法包装在try/catch中并返回一个catch块中的错误异常。您无需复制async状态机的其余逻辑。

因此,假设您希望与服务方法的行为具有相同的异常语义(我怀疑这样做),则可以将这些方法与同步方法完全一样,也就是说,使用适当的方法来调用该方法。参数并返回结果。