Task<T>返回结果的扩展方法

Task<T> Extension Method for Returing Result

想知道,像这样为 Task<T> 使用扩展方法会不会有任何陷阱:

public static T Await<T>(this Task<T> task)
{
    var result = default(T);

    Task.Run(async () => result = await task).Wait();

    return result;
}

对于那些您希望从 Task 获得结果但您使用的方法未标记为 async 的情况,这似乎是一个不错的节省时间的方法。

您的代码将无法正常工作,因为您将 "Hot Task" 传递给函数。

我假设你这样做的原因是为了防止只调用 task.Result 的死锁。发生死锁的原因是您阻塞了 UI 线程,并且任务捕获的同步上下文使用 UI 线程进行回发。问题是上下文是在任务开始时捕获的,而不是在您等待它时捕获的。

所以如果你在你的 UI 线程上做了

Task<Foo> task = SomeMethodAsync();
Foo result = task.Await();

你仍然会陷入僵局,因为 SomeMethodAsync() 捕获的 SynchronizationContext 是 UI 上下文,而 SomeMethodAsync() 中的任何内部 await不使用 .ConfiguerAwait(false) 将尝试使用 UI 线程,该线程将被您在 Await().

中的 .Wait() 调用阻塞

可靠地让它工作的唯一方法是如果该方法采用 Func<Task<T>> 而不是 Task<T>,然后您可以在后台线程中启动任务以确保同步上下文未设置。

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