我最近第一次使用异步(和.Net 4.5),我遇到过让我难过的东西。关于我可以在网上找到的VoidTaskResult课程的信息不多,所以我来到这里是为了看看是否有人对这是怎么回事。
我的代码如下所示。显然,这很简单。基本思想是调用异步的插件方法。如果它们返回Task,则异步调用没有返回值。如果他们返回任务<>,那么就有。我们事先不知道它们是哪种类型,因此我们的想法是使用反射查看结果的类型(如果类型为Type<>,则IsGenericType为true)并使用动态类型获取值。
在我的真实代码中,我通过反射调用插件方法。我认为这不应该对我所看到的行为产生影响。
// 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为false(正如预期的那样)。
但是如果你稍微改变插件方法的声明,IsGenericType现在返回true并且东西断开:
public async Task yada()
{
// stuff
}
执行此操作时,行上会抛出以下异常(object result = dynTask.Result;):
如果您深入了解任务对象,它实际上似乎是Type。 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?”,或者“为什么当我动态调用异步方法时会发生这种奇怪的事情?”甚至可能“如何调用可选的异步插件方法?”在任何情况下,我都把它放在那里,希望其中一位大师能够解决这些问题。
答案 0 :(得分:13)
这是由于设计任务(特别是任务完成源)的类层次结构的方式。
首先,Task<T>
来自Task
。我假设你已经熟悉了。
此外,您可以为执行代码的任务创建Task
或Task<T>
类型。例如,如果您的第一个示例正在返回Task.Run
或诸如此类的东西,那么这将返回一个实际的Task
对象。
当您考虑TaskCompletionSource<T>
如何与任务层次结构进行交互时,会出现此问题。 TaskCompletionSource<T>
用于创建不执行代码的任务,而是充当某些操作已完成的通知。例如,超时,I / O包装或async
方法。
没有非通用TaskCompletionSource
类型,因此如果您想要这样的通知而没有返回值(例如,超时或async Task
方法),那么您必须创建一个{{ 1}}对于某些TaskCompletionSource<T>
并返回T
。 Task<T>
小组必须为async
方法选择T
,因此他们创建了async Task
类型。
通常这不是问题。由于VoidTaskResult
派生自Task<T>
,因此该值会转换为Task
,并且每个人都很满意(在静态世界中)。但是,Task
创建的每个任务实际上都是TaskCompletionSource<T>
类型,而不是Task<T>
,您可以通过反射/动态代码看到这一点。
最终结果是,您必须像对待Task
一样对待Task<VoidTaskResult>
。但是,Task
是一个实现细节;它可能会在未来发生变化。
因此,我建议您实际将逻辑建立在VoidTaskResult
的(声明的)返回类型上,而不是(实际)返回值。这更接近于模仿编译器的作用。
yada