12

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

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

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

// 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;

RuntimeBinderException

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

voidTaskResult 任务

我尝试更改调用代码:

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?”,或者“为什么当我动态调用异步方法时会发生这种奇怪的事情?” 甚至可能是“你如何调用可选异步的插件方法?” 无论如何,我把它放在那里是希望其中一位大师能够阐明一些观点。

4

1 回答 1

16

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

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

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

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

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

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

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

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

Task task = (Task)yadaMethod.Invoke(...);
await task;

if (yadaMethod.ReturnType.IsGenericType)
{
  ...
}
于 2013-07-24T11:21:18.770 回答