【发布时间】:2020-05-15 16:10:22
【问题描述】:
我正在编写一个 .NET Standard 2.0 库,该库将具有相同功能的同步和异步版本,其中一项功能需要延迟取消支持。
我正在尝试想出一种方法来等待特定的时间量,这种方法在所有情况下都不会出现死锁或其他问题。一个担心是waiting synchronously on async methods can cause deadlocks。
考虑这个类,你应该如何实现Wait 以使其在任何地方都安全?调用WaitAsync不是必须的,等待可以完全分开实现。
class Waiter
{
// Easy enough
public async Task WaitAsync(TimeSpan delay, CancellationToken token = default)
{
try
{
await Task.Delay(delay, token)
}
catch(OperationCancelledException)
{
}
}
// Not so straightforward
public void Wait(TimeSpan delay, CancellationToken token = default)
{
WaitAsync(delay, token).GetAwaiter().GetResult(); // May deadlock
WaitAsync(delay, token).Wait(); // May deadlock, also doesn't propagate exceptions properly
WaitAsync(delay).Wait(token); // Even worse
Thread.Sleep(delay) // Doesn't support cancellation
token.WaitHandle.WaitOne(delay, true); // Maybe? Not sure how the second parameter works when I have no control over the context
token.WaitHandle.WaitOne(delay, false); // No idea
}
}
【问题讨论】:
-
我认为如果你配置任务不在同一上下文中恢复,那么同步等待是安全的。例如,`var task = AwaitAsync(delay, token); task.ConfigureAwait(false); task.Wait();` 但是,如果代码执行未在捕获的上下文中恢复,您可能会遇到线程同步问题。尤其是 GUI 应用程序不允许其他非 GUI 线程修改视图
-
“一个担心是在异步方法上同步等待会导致死锁” -- 是的,这是一个担心。但是,这并不意味着你应该担心它。你的 API 要么自然是异步的,要么是同步的;以自然的方式暴露它。让客户担心如何以另一种方式调用它。例如,客户端可以使用
Task.Run()包装您的自然同步 API。或者您的自然异步 API 可以通过调用Wait()或Result被客户端阻止,让他们担心并解决这样做所涉及的危险。 -
@MarcGravell 等待异步方法不是必需的,我只想等待特定的时间。方法
Wait不必调用WaitAsync -
@zafar:OP 可以澄清,但我对该选项的理解是他们依赖于
delay,它会在一段时间后超时等待。 IE。它们使用delay实现“等待”,并使用句柄表示取消。 -
“将同时具有相同功能的同步和异步版本”——这是一个根本性的错误。见标记重复。如果您想要回答有关等待的更具体的问题,您需要发布更具体的问题。从您的问题中甚至不清楚您是否字面上想要等待一段时间,如果是的话,为什么(这有什么意义?)。即使你这样做了,因为等待句柄实际上是实现带取消的阻塞线程等待的常用方法,你应该尝试过,然后解释为什么这对你不起作用。
标签: c# .net delay deadlock cancellation