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;
}
想知道,像这样为 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;
}