What is the type VoidTaskResult as it relates to a

2019-06-18 04:02发布

问题:

I've been using async (and .Net 4.5 really) for the first time recently, and I've come across something that has me stumped. There isn't much information about the VoidTaskResult class that I can find on the Net so I came here to see if anyone has any ideas about what is going on.

My code is something like the following. Obviously, this is much simplified. The basic idea is to call plugin methods, which are asyncronous. If they return Task, then there is no return value from the async call. If they return Task<>, then there is. We don't know in advance which type they are, so the idea is to look at the type of the result using reflection (IsGenericType is true if the type is Type<>) and get the value using a dynamic type.

In my real code, I am calling the plugin method via reflection. I don't think this should make a difference to the behaviour I am seeing.

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

This works good for the plugin method shown above. IsGenericType is false (as expected).

However if you change the declaration of the plugin method ever so slightly, IsGenericType now returns true and stuff breaks:

public async Task yada()
{
 // stuff
}

When you do this, the following exception is thrown on the line (object result = dynTask.Result;):

If you dig into the task object, it actually appears to be Type. VoidTaskResult is a private type in the Threading name space with almost nothing in it.

I tried changing my calling code:

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
  }
}

This "succeeds" in the sense that it no longer throws, but now result is of the type "VoidTaskResult" which I cannot sensibly do anything with.

I should add that I'm having a hard time even formulating a real question for all this. Maybe my real question is something like "What is VoidTaskResult?", or "Why does this weird thing happen when I call an async method dynamically?" or possibly even "How do you call plugin methods that are optionally asyncronous?" In any case, I am putting this out there in the hope that one of the gurus will be able to shed some light.

回答1:

This is due to the way the class hierarchy around tasks (and particularly task completion sources) is designed.

First off, Task<T> derives from Task. I assume you're already familiar with that.

Furthermore, you can create types of Task or Task<T> for tasks that execute code. E.g., if your first example was returning Task.Run or whatnot, then that would be returning an actual Task object.

The problem comes in when you consider how TaskCompletionSource<T> interacts with the task hierarchy. TaskCompletionSource<T> is used to create tasks that don't execute code, but rather act as a notification that some operation has completed. E.g., timeouts, I/O wrappers, or async methods.

There is no non-generic TaskCompletionSource type, so if you want to have notification like this without a return value (e.g., timeouts or async Task methods), then you have to create a TaskCompletionSource<T> for some T and return the Task<T>. The async team had to choose a T for async Task methods, so they created the type VoidTaskResult.

Normally this is not a problem. Since Task<T> derives from Task, the value is converted to Task and everyone is happy (in the static world). However, every task created by TaskCompletionSource<T> is actually of type Task<T>, not Task, and you see this with reflection/dynamic code.

The end result is that you have to treat Task<VoidTaskResult> just like it was Task. However, VoidTaskResult is an implementation detail; it may change in the future.

So, I recommend that you actually base your logic on the (declared) return type of yada, not the (actual) return value. This more closely mimics what the compiler does.

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

if (yadaMethod.ReturnType.IsGenericType)
{
  ...
}