I was wondering, would there be any gotchas from using an extension method for Task<T>
like so:
public static T Await<T>(this Task<T> task)
{
var result = default(T);
Task.Run(async () => result = await task).Wait();
return result;
}
It seems like a decent time saver for those instances where you would like to get the result from a Task
but you're in a method that is not marked with async
.
You code will not work like you want because you are passing in a "Hot Task" to the function.
I assume the reason you are doing this is to prevent the deadlock of just calling task.Result
. The reason the deadlock happens is you are blocking the UI thread and the task's captured sync context uses the UI thread for it's postbacks. The problem is the context is captured when the task starts not when you await it.
So if you did on your UI thread
Task<Foo> task = SomeMethodAsync();
Foo result = task.Await();
you are still going to deadlock because the SynchronizationContext
that SomeMethodAsync()
captured is the UI context, and any internal await
inside SomeMethodAsync()
that does not use .ConfiguerAwait(false)
will try to use the UI thread which will be blocked by your .Wait()
call in Await()
.
The only way to reliably get it to work is if the method took in a Func<Task<T>>
instead of just Task<T>
, you could then start the task in the background thread to ensure the sync context is not set.
public static T BlockWithoutLockup<T>(Func<Task<T>> task)
{
T result;
if(SynchronizationContext.Current != null)
{
//We use ".GetAwaiter().GetResult()" instead of .Result to get the exception handling
// like we would if we had called `await` or the function directly.
result = Task.Run(task).GetAwaiter().GetResult();
}
else
{
//If we are on the default sync context already just run the code, no need to
// spin up another thread.
result = task().GetAwaiter().GetResult();
}
return result;
}