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;
:
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.
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.
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.
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.
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.
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.
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)
{
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With