【问题标题】:How Do I Create a Looping Service inside an C# Async/Await application?如何在 C# Async/Await 应用程序中创建循环服务?
【发布时间】:2018-08-01 21:25:07
【问题描述】:

我编写了一个类,该类具有在线程池中作为长时间运行的任务运行的方法。该方法是一种监视服务,用于定期发出 REST 请求以检查另一个系统的状态。这只是一个带有 try()catch() 的 while() 循环,以便它可以处理自己的异常,并在发生意外情况时优雅地继续。

这是一个例子:

public void LaunchMonitorThread()
{
    Task.Run(() =>
    {
        while (true)
        {
            try
            {
                //Check system status
                Thread.Sleep(5000);
            }
            catch (Exception e)
            {
                Console.WriteLine("An error occurred. Resuming on next loop...");
            }
        }
    });
}

它工作正常,但我想知道是否有另一种模式可以让 Monitor 方法作为标准 Async/Await 应用程序的常规部分运行,而不是使用 Task.Run() 启动它——基本上我试图避免即发即弃的模式。

所以我尝试将代码重构为:

   public async Task LaunchMonitorThread()
    {

        while (true)
        {
            try
            {
                //Check system status

                //Use task.delay instead of thread.sleep:
                await Task.Delay(5000);
            }
            catch (Exception e)
            {
                Console.WriteLine("An error occurred. Resuming on next loop...");
            }
        }

    }

但是当我尝试在另一个异步方法中调用该方法时,我收到了有趣的编译器警告:

“由于不等待此调用,因此在调用完成之前继续执行当前方法。”

现在我认为这是正确的,也是我想要的。但我有疑问,因为我是异步/等待的新手。 这段代码会按我预期的方式运行,还是会死机或做其他致命的事情?

【问题讨论】:

  • 这可能会有所帮助:stackoverflow.com/questions/50914302/…
  • 不管怎样,这不还是一劳永逸吗?这不是你想要的吗?
  • 如果你在没有等待的情况下调用这个任务返回方法,你仍然是在做一劳永逸的事情。这就是编译器警告的含义。主要区别在于,在第二个示例中,监控逻辑将安排在主上下文/线程上,而第一个示例将使其在线程池上运行。虽然它不应该阻塞线程

标签: c# asynchronous async-await


【解决方案1】:

您真正需要的是Timer 的使用。使用System.Threading 命名空间中的那个。无需使用Task 或其任何其他变体(对于您显示的代码示例)。

private System.Threading.Timer timer;
void StartTimer()
{
    timer = new System.Threading.Timer(TimerExecution, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
}

void TimerExecution(object state)
{
    try
    {
        //Check system status
    }
    catch (Exception e)
    {
        Console.WriteLine("An error occurred. Resuming on next loop...");
    }
}

来自documentation

提供一种机制,以指定的时间间隔在线程池线程上执行方法


您也可以使用System.Timers.Timer,但您可能不需要它。有关 2 个计时器之间的比较,另请参阅 System.Timers.Timer vs System.Threading.Timer

【讨论】:

    【解决方案2】:

    如果您需要即发即弃操作,那很好。我建议用CancellationToken改进它

    public async Task LaunchMonitorThread(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            try
            {
                //Check system status
    
                //Use task.delay instead of thread.sleep:
                await Task.Delay(5000, token);
            }
            catch (Exception e)
            {
                Console.WriteLine("An error occurred. Resuming on next loop...");
            }
        }
    
    }
    

    除此之外,你可以像这样使用它

    var cancellationToken = new CancellationToken();
    var monitorTask = LaunchMonitorThread(cancellationToken);
    

    并保存任务和/或取消令牌以在您想要的任何地方中断监视器

    【讨论】:

    • 这非常有帮助。谢谢。
    【解决方案3】:

    您用来触发的方法 Task.Run 非常适合从非异步方法启动长时间运行的异步函数。

    你是对的:忘记部分是不正确的。例如,如果您的进程将要关闭,如果您请求启动的线程完成其任务会更整洁。

    正确的做法是使用CancellationTokenSource。如果您将CancellationTokenSource 订购到Cancel,那么从这个CancellationTokenSource 使用Tokens 启动的所有程序都将在合理的时间内停止。

    所以让我们创建一个类LongRunningTask,它将在构造时创建一个长时间运行的Task,并在Dispose() 时使用CancellationTokenSource 创建Cancel 这个任务。

    CancellationTokenSourceTask 都实现了IDisposable,当LongRunningTask 对象被释放时,简洁的方法是Dispose 这两个

    class LongRunningTask : IDisposable
    {
        public LongRunningTask(Action<CancellationToken> action)
        {   // Starts a Task that will perform the action
            this.cancellationTokenSource = new CancellationTokenSource();
            this.longRunningTask = Task.Run( () => action (this.cancellationTokenSource.Token));
        }
    
        private readonly CancellationTokenSource cancellationTokenSource;
        private readonly Task longRunningTask;
        private bool isDisposed = false;
    
        public async Task CancelAsync()
        {   // cancel the task and wait until the task is completed:
            if (this.isDisposed) throw new ObjectDisposedException();
    
            this.cancellationTokenSource.Cancel();
            await this.longRunningTask;
        }
    
        // for completeness a non-async version:
        public void Cancel()
        {   // cancel the task and wait until the task is completed:
            if (this.isDisposed) throw new ObjectDisposedException();
    
            this.cancellationTokenSource.Cancel();
            this.longRunningTask.Wait;
        }
    }
    

    添加标准的处置模式

    public void Dispose()
    {
         this.Dispose(true);
         GC.SuppressFinalize(this);
    }
    
    protected void Dispose(bool disposing)
    {
        if (disposing && !this.isDisposed)
        {   // cancel the task, and wait until task completed:
            this.Cancel();
            this.IsDisposed = true;                 
        }
    }
    

    用法:

    var longRunningTask = new LongRunningTask( (token) => MyFunction(token)
    ...
    // when application closes:
    await longRunningTask.CancelAsync(); // not necessary but the neat way to do
    longRunningTask.Dispose();
    

    Action {...} 有一个CancellationToken 作为输入参数,你的函数应该定期检查它

    async Task MyFunction(CancellationToken token)
    {
         while (!token.IsCancellationrequested)
         {
              // do what you have to do, make sure to regularly (every second?) check the token
              // when calling other tasks: pass the token
              await Task.Delay(TimeSpan.FromSeconds(5), token);
         }
    }
    

    您可以调用token.ThrowIfCancellationRequested,而不是检查令牌。这将引发您必须捕获的异常

    【讨论】:

      猜你喜欢
      • 2017-05-16
      • 1970-01-01
      • 2019-07-16
      • 1970-01-01
      • 1970-01-01
      • 2013-05-16
      • 2016-12-30
      • 1970-01-01
      • 2020-05-30
      相关资源
      最近更新 更多