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

时间:2018-07-31 16:54:23

标签: c# asynchronous async-await

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

这是一个例子:

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...");
            }
        }

    }

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

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

现在我认为这是正确的,也是我想要的。但是我有疑问,因为我是异步/等待的新手。 此代码将按照我期望的方式运行,还是将DEADLOCK锁定或执行其他致命操作?

3 个答案:

答案 0 :(得分: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,但可能不需要。有关2个计时器之间的比较,另请参见System.Timers.Timer vs System.Threading.Timer

答案 1 :(得分: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);

并保存任务和/或cancelingToken,以便在任何需要的地方中断监视

答案 2 :(得分:1)

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

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

执行此操作的正确方法是使用CancellationTokenSource。如果您将CancellationTokenSource设置为Cancel,则从Tokens开始使用CancellationTokenSource的所有过程将在合理的时间内完全停止。

因此,我们创建一个类LongRunningTask,该类将在构建时创建一个运行时间长的Task,并在Dispose()上使用Cancel来创建CancellationTokenSource这个任务。

由于CancellationTokenSourceTask都实现了IDisposable,所以整洁的方法是在放置Dispose对象时LongRunningTask这两个{p >

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而不是检查令牌。这将引发您必须捕捉的异常