C# · 12月 22, 2021

c# – 与异步方法有关的VoidTaskResult类型是什么?

我最近第一次使用异步(和.Net 4.5),我遇到了让我难过的东西.关于我可以在网上找到的VoidTaskResult类的信息不多,所以我来到这里是为了看看是否有任何关于发生了什么的想法.

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

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

// plugin methodpublic 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;):

如果你深入研究任务对象,它实际上似乎是类型. 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?”,或者“为什么当我动态调用异步方法时会发生这种奇怪的事情?”甚至可能“如何调用可选的异步插件方法?”在任何情况下,我都把它放在那里,希望其中一位大师能够解决这些问题.

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

首先,任务< T>派生自任务.我假设你已经熟悉了.

此外,您可以创建任务或任务的类型< T>用于执行代码的任务.例如,如果你的第一个例子是返回Task.Run或whatnot,那么那将返回一个实际的Task对象.

当您考虑TaskCompletionSource< T>的方式时会出现问题.与任务层次结构交互. TaskCompletionSource< T>用于创建不执行代码的任务,而是充当某些操作已完成的通知.例如,超时,I / O包装或异步方法.

没有非通用的TaskCompletionSource类型,因此如果您希望这样的通知没有返回值(例如,超时或异步任务方法),那么您必须创建一个TaskCompletionSource< T>对于某些T并返回任务< T>.异步团队必须为异步任务方法选择一个T,因此他们创建了类型VoidTaskResult.

通常这不是问题.由于任务< T>派生自Task,将值转换为Task,每个人都很开心(在静态世界中).但是,TaskCompletionSource< T>创建的每个任务都是由TaskCompletionSource创建的.实际上是任务< T>类型,而不是任务,你看到这与反射/动态代码.

最终结果是你必须处理Task< VoidTaskResult>就像是Task.但是,VoidTaskResult是一个实现细节;它可能会在未来发生变化.

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

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