使用 Monitor.Wait() 和 Monitor.Pulse() 暂停/恢复长时间运行的任务(while(true

本文关键字:Monitor 运行 任务 while 长时间 true 暂停 Wait Pulse 使用 恢复 | 更新日期: 2023-09-27 18:36:39

我有以下代码,它充当异步消费者:

private readonly Object sync;
private async void ConsumeAsync()
{
    // The reason why I have an await here is to yield the control to the caller, e.g: if an exception happens inside the Listen method, it's being propagated and can be handled in caller codes.
    await Task.Run(() => this.Listen());
}
private void Listen()
{
    ...
    while (true)
    {
        lock (sync)
        {
            while (<blocking-condition>)
            {
                Monitor.Wait(sync);
            }
        }
        // Consuming happens here
    }
}
// Changing the blocking condition happens outside the infinite loop with proper Monitor.Pulse usage

我的设计问题:

  1. 我有一个异步空洞即发即弃的方法,如果没有消耗,则必须阻止它。这是一种正确的方法吗?
  2. 我是否必须对任务使用"长时间运行"选项?
  3. 我必须使用取消令牌吗?
  4. 我是否正确地将异常传播到更高级别的异常处理程序?

使用 Monitor.Wait() 和 Monitor.Pulse() 暂停/恢复长时间运行的任务(while(true

你的四个问题中的两个对于这个论坛来说太宽泛了。只有您可以决定这是否是适合你的方案的"正确方法"。同样,是否使用CancellationToken的问题取决于您需要哪些功能。如果您需要能够取消操作,这可能是一个好方法,但不是唯一的方法,这里没有足够的细节来了解什么是最好的。你也无法提供足够的细节,同时为这个论坛保持问题足够狭窄。

当然,如果您不需要能够取消操作,则该问题的答案是"不,您不需要使用CancellationToken":)。

至于另外两个问题,只有一个有明确的答案:

  1. "我是否必须对任务使用长时间运行选项?"

不,您不必这样做。但强烈建议用于实际上长时间运行的任务。这将有助于任务计划框架正确管理任务的线程。

  1. "我是否将异常正确传播到更高级别的异常处理程序?"

你是否值得怀疑。由于您的ConsumeAsync()方法是async void,您显然不能等待它。做这种事情的自然方法是编写更像这样的代码:

private Task ConsumeAsync()
{
    return Task.Run(() => this.Listen());
}
async Task SomeMethodSomewhereElse()
{
    try
    {
        await ConsumeAsync();
    }
    catch (...)
    {
        // An exception thrown from your long-running task will be caught here
    }
}

请注意,ConsumeAsync()方法本身实际上并不需要async。您可以只返回已创建的Task,并且将完全相同地工作(嗯,更有效......但最主要的只是不要添加完全不必要的额外代码)。

按照你编写它的方式,你必须做很多额外的工作来捕获抛出的异常(即直接从SynchronizationContext处理它)。也许你做到了,也许你没有。你没有显示该代码,所以我假设你没有。