【问题标题】:Is it correct to pause/resume a long running Task (while(true)) with Monitor.Wait() and Monitor.Pulse()?使用 Monitor.Wait() 和 Monitor.Pulse() 暂停/恢复长时间运行的任务 (while(true)) 是否正确?
【发布时间】:2015-01-09 05:00:32
【问题描述】:

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

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. 我有一个异步 void 即发即弃的方法,如果没有消耗,则必须阻止该方法。这是一个正确的方法吗?
  2. 我必须对任务使用 LongRunning 选项吗?
  3. 我必须使用 CancellationToken 吗?
  4. 我是否将异常正确地传播到更高级别的异常处理程序?

【问题讨论】:

  • 这会有所帮助。 Cooperatively pausing async methods
  • 最佳实践是avoid async void methods - 作为“当异步任务或异步任务方法抛出异常时,该异常被捕获并放置在任务对象上。使用异步无效方法,没有 Task 对象,因此从异步 void 方法抛出的任何异常都将直接在异步 void 方法启动时处于活动状态的 SynchronizationContext 上引发。" ...
  • 但是如果我使用 BlockingCollection 或其他 3rd 方集合(如 RabbitMQ 共享队列),它可以在 while (true) 循环中使用 Monitor.Wait 阻塞其调用线程?

标签: c# multithreading task-parallel-library async-await


【解决方案1】:

您的四个问题中有两个对于本论坛来说过于宽泛。只有您可以决定这是否适合您的场景。同样,是否使用CancellationToken 的问题取决于您需要什么功能。如果您需要能够取消操作,这可能是一个好方法,但不是唯一的方法,而且这里没有足够的细节来知道什么是最好的。你也不能提供足够的细节,同时让这个论坛的问题足够窄。

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

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

  1. “我必须对任务使用 LongRunning 选项吗?”

不,你没有。但强烈建议用于实际上长期运行的任务。这将有助于任务调度框架正确管理您的任务线程。

  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 处理它)。也许你这样做了,也许你没有。您没有显示该代码,所以我假设您没有。

【讨论】:

  • 感谢您的简短回答。您对使用 Monitor.Wait 暂停 Listen 方法有何看法?
  • 如果您要阻止线程,Monitor.Wait() 是一个不错的选择,前提是它满足您的需求。也就是说,方法名称ConsumeAsync() 暗示了一种生产者/消费者模式,BlockingCollection&lt;T&gt; 类型非常适合这种模式。如果您使用该类,则根本不需要任何显式同步。
猜你喜欢
  • 1970-01-01
  • 2017-02-15
  • 1970-01-01
  • 2013-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-11
相关资源
最近更新 更多