同步调用第三方异步任务方法
Calling a 3rd party async Task method synchronously
我正在阅读数百个关于 await
/async
的答案,但仍然没有找到这个简单问题的答案。
有个awaitable方法,别人写的,需要等到完成。
// cannot be changed
public async Task ThirdPartyAsync(...)
{
// calls other awaitable methods
// takes few seconds
}
// I'd like something like this
public void MyPatientCallingMethod(...)
{
// 1. call the method
ThirdPartyAsync(...);
// 2. wait for ThirdPartyAsync to finish (I don't care how long)
// 3. return after
return;
}
是的,我知道,它会阻塞主线程、UI 线程、任何其他线程...
为了安全起见,必须这样解决。
问题是async
方法的return值为Task
,没有.Result
标签等待
还有其他无死锁的解决方案吗?
您可以使用Task.Wait()
来阻止任务。
// call the method and wait for ThirdPartyAsync to finish (I don't care how long)
ThirdPartyAsync(...).Wait();
没有通用参数的任何 Task
缺少 Result
因为它实际上是 void
return - 是 没有Result
等待中。
相反,您要查找的是 Task.Wait()
(如其他答案中所述)。
您可以使用:
Wait()
- 如果任务出错,将抛出 AggregateException
。
GetAwaiter().GetResult()
- 会抛出第一个异常(就像 await
那样)。
- Stephen Cleary's
AsyncContext
:
public void MyPatientCallingMethod(...)
{
AsyncContext.Run(() => ThirdPartyAsync(...));
}
The AsyncContext
type provides a context for executing asynchronous operations. The await
keyword requires a context to return back to; for most programs, this is a UI context, and you don't have to worry about it. ASP.NET also provides a proper context and you don't have to use your own.
However, Console applications and Win32 services do not have a suitable context, and AsyncContext
could be used in those situations.
从评论中可以明显看出误解是什么:您假设 await
启动了一个新线程并继续执行。相反的情况是:await
暂停执行直到它的参数完成。您正在调用的 Task
-returning 方法应该启动异步。 await
消费那个。
使用await
。它现在是在不阻塞 UI.
的情况下执行长时间 运行 工作的标准方法
那是因为最佳答案是 "don't"。你真的应该一直努力做到异步。
I can't use await because I have to be sure the method is finished before I do anything else.
请注意 async
是串行的(这意味着该方法不会继续通过 await
直到内部方法完成);它只是异步串行而不是同步串行。所以同样的编程结构和逻辑同样适用:
public async Task MyPatientCallingMethodAsync(...)
{
// 1. call the method
var task = ThirdPartyAsync(...);
// 2. wait for ThirdPartyAsync to finish
await task;
// 3. return after
return;
}
这正是您应该在绝大部分时间 绝大部分 中采用的方法。
但是,如果您确定绝对不能这样做,那么您的选择是有限的。 Stephen Toub describes your options on his blog.
请务必注意,none 选项适用于所有场景,并且每个选项都有缺点和陷阱.所以我们 none 可以说出哪一个是最好的,甚至哪个可以 工作 因为我们不知道你的代码在什么上下文中执行。
总而言之,您的选择是:
- 使用
GetAwaiter().GetResult()
或 Wait
同步阻止(有关差异,请参阅@l3arnon 的回答)。例如。 ThirdPartyAsync(...).GetAwaiter().GetResult();
。这种方法的缺点是,如果在 UI 或 ASP.NET 上下文中调用,则很可能会出现死锁。
- 卸载到线程池线程并在其上阻塞。例如,
Task.Run(() => ThirdPartyAsync(...)).GetAwaiter().GetResult();
。这种方法的缺点是第三方代码 必须 运行 在备用线程上,因此这不适用于假定 UI 或 ASP.NET上下文。
- 执行嵌套消息循环。例如,
AsyncContext.Run(() => ThirdPartyAsync(...));
。这种方法的缺点是嵌套循环可能无法完成您需要的所有事情(即,它可能不会抽取第三方代码可能需要的 Win32 消息),嵌套上下文与预期上下文不匹配(即,这可能会导致ASP.NET 调用失败),并且嵌套循环可能会做太多事情(即,它可能会发送 Win32 消息,这会导致意外的重入)。
简而言之,虽然听起来很简单,但 "sync over async" 是您可以(尝试)做的最复杂的事情之一。这是 "just use async all the way!" 的常见答案背后的长解释:)
我正在阅读数百个关于 await
/async
的答案,但仍然没有找到这个简单问题的答案。
有个awaitable方法,别人写的,需要等到完成。
// cannot be changed
public async Task ThirdPartyAsync(...)
{
// calls other awaitable methods
// takes few seconds
}
// I'd like something like this
public void MyPatientCallingMethod(...)
{
// 1. call the method
ThirdPartyAsync(...);
// 2. wait for ThirdPartyAsync to finish (I don't care how long)
// 3. return after
return;
}
是的,我知道,它会阻塞主线程、UI 线程、任何其他线程... 为了安全起见,必须这样解决。
问题是async
方法的return值为Task
,没有.Result
标签等待
还有其他无死锁的解决方案吗?
您可以使用Task.Wait()
来阻止任务。
// call the method and wait for ThirdPartyAsync to finish (I don't care how long)
ThirdPartyAsync(...).Wait();
没有通用参数的任何 Task
缺少 Result
因为它实际上是 void
return - 是 没有Result
等待中。
相反,您要查找的是 Task.Wait()
(如其他答案中所述)。
您可以使用:
Wait()
- 如果任务出错,将抛出AggregateException
。GetAwaiter().GetResult()
- 会抛出第一个异常(就像await
那样)。- Stephen Cleary's
AsyncContext
:
public void MyPatientCallingMethod(...)
{
AsyncContext.Run(() => ThirdPartyAsync(...));
}
The
AsyncContext
type provides a context for executing asynchronous operations. Theawait
keyword requires a context to return back to; for most programs, this is a UI context, and you don't have to worry about it. ASP.NET also provides a proper context and you don't have to use your own.
However, Console applications and Win32 services do not have a suitable context, andAsyncContext
could be used in those situations.
从评论中可以明显看出误解是什么:您假设 await
启动了一个新线程并继续执行。相反的情况是:await
暂停执行直到它的参数完成。您正在调用的 Task
-returning 方法应该启动异步。 await
消费那个。
使用await
。它现在是在不阻塞 UI.
那是因为最佳答案是 "don't"。你真的应该一直努力做到异步。
I can't use await because I have to be sure the method is finished before I do anything else.
请注意 async
是串行的(这意味着该方法不会继续通过 await
直到内部方法完成);它只是异步串行而不是同步串行。所以同样的编程结构和逻辑同样适用:
public async Task MyPatientCallingMethodAsync(...)
{
// 1. call the method
var task = ThirdPartyAsync(...);
// 2. wait for ThirdPartyAsync to finish
await task;
// 3. return after
return;
}
这正是您应该在绝大部分时间 绝大部分 中采用的方法。
但是,如果您确定绝对不能这样做,那么您的选择是有限的。 Stephen Toub describes your options on his blog.
请务必注意,none 选项适用于所有场景,并且每个选项都有缺点和陷阱.所以我们 none 可以说出哪一个是最好的,甚至哪个可以 工作 因为我们不知道你的代码在什么上下文中执行。
总而言之,您的选择是:
- 使用
GetAwaiter().GetResult()
或Wait
同步阻止(有关差异,请参阅@l3arnon 的回答)。例如。ThirdPartyAsync(...).GetAwaiter().GetResult();
。这种方法的缺点是,如果在 UI 或 ASP.NET 上下文中调用,则很可能会出现死锁。 - 卸载到线程池线程并在其上阻塞。例如,
Task.Run(() => ThirdPartyAsync(...)).GetAwaiter().GetResult();
。这种方法的缺点是第三方代码 必须 运行 在备用线程上,因此这不适用于假定 UI 或 ASP.NET上下文。 - 执行嵌套消息循环。例如,
AsyncContext.Run(() => ThirdPartyAsync(...));
。这种方法的缺点是嵌套循环可能无法完成您需要的所有事情(即,它可能不会抽取第三方代码可能需要的 Win32 消息),嵌套上下文与预期上下文不匹配(即,这可能会导致ASP.NET 调用失败),并且嵌套循环可能会做太多事情(即,它可能会发送 Win32 消息,这会导致意外的重入)。
简而言之,虽然听起来很简单,但 "sync over async" 是您可以(尝试)做的最复杂的事情之一。这是 "just use async all the way!" 的常见答案背后的长解释:)