Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the type VoidTaskResult as it relates to async methods?

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 asynchronous. 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;:

RuntimeBinderException

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

VoidTaskResult task

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 asynchronous?" In any case, I am putting this out there in the hope that one of the gurus will be able to shed some light.

like image 333
1800 INFORMATION Avatar asked Jul 24 '13 03:07

1800 INFORMATION


People also ask

What is the return type of async await in C#?

Starting with C# 7.0, an async method can return any type that has an accessible GetAwaiter method that returns an instance of an awaiter type. In addition, the type returned from the GetAwaiter method must have the System. Runtime. CompilerServices.

What does async keyword in a method signify?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.

How to define async method in C#?

An async method runs synchronously until it reaches its first await expression, at which point the method is suspended until the awaited task is complete. In the meantime, control returns to the caller of the method, as the example in the next section shows.

Which data can an async method return?

The async method returning Task in C# Such type of async methods returns void if they run synchronously. If we have an async method with Task return type and if we want our caller method to wait until the async method completes its execution then we need to use the await operator while calling the async method.


1 Answers

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)
{
  ...
}
like image 64
Stephen Cleary Avatar answered Nov 15 '22 22:11

Stephen Cleary