【问题标题】:What is the type VoidTaskResult as it relates to async methods?与异步方法相关的 VoidTaskResult 类型是什么?
【发布时间】:2019-11-28 10:14:21
【问题描述】:

我最近第一次使用异步(真的是 .Net 4.5),我遇到了一些让我难过的事情。我在网上找不到太多关于 VoidTaskResult 课程的信息,所以我来这里看看是否有人对发生的事情有任何想法。

我的代码如下所示。显然,这被简化了很多。基本思想是调用插件方法,它们是异步的。如果它们返回Task,则异步调用没有返回值。如果他们返回Task<>,那就有。我们事先不知道它们是哪种类型,所以想法是使用反射查看结果的类型(如果类型为Type<>,则IsGenericType 为真)并使用动态类型获取值。

在我的真实代码中,我通过反射调用插件方法。我认为这不会对我所看到的行为产生影响。

// plugin method
public Task yada()
{
 // stuff
}

public async void doYada()
{
  Task task = yada();
  await task;

  if (task.GetType().IsGenericType)
  {
    dynamic dynTask = task;
    object result = dynTask.Result;
    // do something with result
  }
}

这适用于上面显示的插件方法。 IsGenericType 为假(如预期)。

但是,如果您稍微更改插件方法的声明,IsGenericType 现在会返回 true 并且内容会中断:

public async Task yada()
{
 // stuff
}

执行此操作时,object result = dynTask.Result; 行会引发以下异常:

如果您深入研究任务对象,它实际上似乎是Type<VoidTaskResult>VoidTaskResult 是 Threading 命名空间中的私有类型,其中几乎没有任何内容。

我尝试更改我的调用代码:

public async void doYada()
{
  Task task = yada();
  await task;

  if (task.GetType().IsGenericType)
  {
    object result = task.GetType().GetProperty("Result").GetMethod.Invoke(task, new object[] { });
    // do something with result
  }
}

这个“成功”的意思是它不再抛出,但现在结果是 VoidTaskResult 类型,我不能明智地做任何事情。

我应该补充一点,我什至很难为这一切提出一个真正的问题。也许我真正的问题是“什么是VoidTaskResult?”,或者“为什么当我动态调用异步方法时会发生这种奇怪的事情?”甚至可能是“你如何调用可选异步的插件方法?”无论如何,我把它放在那里是希望其中一位大师能够提供一些启示。

【问题讨论】:

    标签: c# dynamic reflection asynchronous


    【解决方案1】:

    这是由于围绕任务(尤其是任务完成源)的类层次结构的设计方式。

    首先,Task<T> 派生自 Task。我想你已经很熟悉了。

    此外,您可以为执行代码的任务创建 TaskTask<T> 类型。例如,如果您的第一个示例返回 Task.Run 或诸如此类,那么这将返回一个实际的 Task 对象。

    当您考虑TaskCompletionSource<T> 如何与任务层次结构交互时,问题就出现了。 TaskCompletionSource<T> 用于创建不执行代码的任务,而是充当某个操作已完成的通知。例如,超时、I/O 包装器或async 方法。

    没有非通用的TaskCompletionSource 类型,所以如果你想在没有返回值的情况下得到这样的通知(例如,超时或async Task 方法),那么你必须为一些@ 创建一个TaskCompletionSource<T> 987654334@ 并返回Task<T>async 团队必须为 async Task 方法选择 T,因此他们创建了 VoidTaskResult 类型。

    通常这不是问题。由于Task<T> 派生自Task,因此值转换为Task,并且每个人都很高兴(在静态世界中)。但是,TaskCompletionSource<T> 创建的每个任务实际上都是Task<T> 类型,而不是Task,您可以通过反射/动态代码看到这一点。

    最终结果是您必须像对待 Task 一样对待 Task<VoidTaskResult>。但是,VoidTaskResult 是一个实现细节;将来可能会改变。

    因此,我建议您实际上将逻辑基于yada 的(声明的)返回类型,而不是(实际的)返回值。这更接近于模仿编译器的工作。

    Task task = (Task)yadaMethod.Invoke(...);
    await task;
    
    if (yadaMethod.ReturnType.IsGenericType)
    {
      ...
    }
    

    【讨论】:

    • 为什么异步团队要引入 VoidTaskResult 而不是仅仅引入非泛型 TaskCompletionSource?
    • @Puppy:我怀疑是因为工作量少了很多。编写一个非泛型的TaskCompletionSource 并不太难,但是任何公共 API 都必须通过一堆其他的“门”才能发布。安全审查等等等等。
    • 这个解释很有道理。但从实际的角度来看,如何在运行时确定没有结果的 Task 和有结果的 Task 之间的区别? IE。由于Task<VoidTaskResult> 甚至用于没有结果的任务,并且由于VoidTaskResult 不是公开的,因此检查的唯一方法似乎是比较Task<T> 类型参数名称。我想这在技术上是可以的,但在这样的代码中必须将类型名称指定为字符串似乎不像 C#。
    • @PeterDuniho:这种运行时代码肯定不像 C#。因为这是编译器永远不需要担心的事情;它总是使用声明的类型。我很想听听你的情况;可能有更好的解决方案。
    • 不是我的方案本身。我在阅读this question 后遇到了这个问题。我整理了一个我认为可以解决问题中特定请求的答案,但是依赖非公共类型的 text 名称的需要让我感到困惑(如果我可以的话,那就太糟糕了'我使用了typeof 运算符,但必须将名称作为文本进行比较对我来说特别烦人)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-21
    • 1970-01-01
    • 1970-01-01
    • 2013-04-11
    • 2015-08-09
    • 1970-01-01
    相关资源
    最近更新 更多