如何在C#Async/Await应用程序中创建循环服务?

如何在C#Async/Await应用程序中创建循环服务?,c#,asynchronous,async-await,C#,Asynchronous,Async Await,我用一个方法编写了一个类,该方法在线程池中作为长期运行的任务运行。该方法是一种监视服务,用于定期发出REST请求以检查另一个系统的状态。它只是一个while()循环,内部有一个try()catch(),这样它就可以处理自己的异常,并在发生意外情况时优雅地继续 下面是一个例子: public void LaunchMonitorThread() { Task.Run(() => { while (true) { try

我用一个方法编写了一个类,该方法在线程池中作为长期运行的任务运行。该方法是一种监视服务,用于定期发出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/Wait应用程序的常规部分运行,而不是使用Task.run()启动它——基本上我是在尝试避免使用fire and forget模式

因此,我尝试将代码重构为:

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

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

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


现在我认为这是正确的,也是我想要的。但我有疑问,因为我是async/Wait新手这段代码是否会按我预期的方式运行,还是会死锁或执行其他致命的操作?

您真正想要的是使用。使用名称空间中的名称。无需使用
任务
或其任何其他变体(对于您显示的代码示例)

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



您也可以使用,但可能不需要它。有关这两个计时器之间的比较,请参见。

如果需要启动并忘记操作,则可以。我建议使用
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);

并将task和/或cancellationToken保存到您想要的任何位置中断监视器

用于激发的方法
task.Run
非常适合从非异步方法启动长时间运行的异步函数

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

正确的方法是使用
CancellationTokenSource
。如果您将
CancellationTokenSource
订购为
Cancel
,则从该
CancellationTokenSource
开始使用
令牌的所有过程将在合理时间内整齐停止

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

由于
CancellationTokenSource
任务
实现
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;
    }
}
用法:

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
,而不是检查令牌。这将抛出一个异常,您必须捕获它。这可能会有所帮助:这不是仍然会触发并忘记吗?这不是你基本上想要的吗?如果你在不等待的情况下调用这个任务返回方法,你仍然在做fire And forget。这就是编译器警告的内容。主要区别在于,在第二个示例中,监视逻辑将在主上下文/线程上调度,而第一个示例将使其在线程池上运行。它不应该阻塞线程,尽管这非常有用。非常感谢。
var longRunningTask = new LongRunningTask( (token) => MyFunction(token)
...
// when application closes:
await longRunningTask.CancelAsync(); // not necessary but the neat way to do
longRunningTask.Dispose();
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);
     }
}