代码之家  ›  专栏  ›  技术社区  ›  JamesHoux

如何在C异步/等待应用程序中创建循环服务?

  •  2
  • JamesHoux  · 技术社区  · 6 年前

    我用一个方法编写了一个类,该方法在线程池中作为长时间运行的任务运行。该方法是一个监控服务,用于定期发出一个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方法作为标准异步/等待应用程序的常规部分运行,而不是用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...");
                }
            }
    
        }
    

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

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

    现在我 认为 这是正确的,我想要什么。但我有疑问,因为我刚接触异步/等待。 这段代码会按我预期的方式运行,还是会死锁,或者做一些其他致命的事情?

    3 回复  |  直到 6 年前
        1
  •  6
  •   Igor    6 年前

    你真正想要的是使用 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 但你可能不需要它。有关两个计时器之间的比较,请参见 System.Timers.Timer vs System.Threading.Timer .

        2
  •  2
  •   Denys Prodan    6 年前

    如果你需要火而忘记手术,那就好了。我建议用 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
  •  1
  •   Harald Coppoolse    6 年前

    方法 Task.Run 您用于激发的是从非异步方法启动长时间运行的异步函数的完美方法。

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

    正确的方法是使用 CancellationTokenSource . 如果您订购 取消标记源 Cancel ,然后开始使用 Tokens 由此 取消标记源 将在合理时间内整齐地停止。

    那么让我们创建一个类 LongRunningTask ,这将创建一个长期运行 Task 在施工和 取消 此任务使用 取消标记源 处理时()

    既是两个 取消标记源 作为 任务 实施 IDisposable 最好的办法是 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();
    

    行动 {...} 有一个 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 . 这将引发一个例外,你必须抓住它