我编写了一个类,该类的方法可以在线程池中作为长期运行的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锁定或执行其他致命操作?
答案 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...");
}
}
提供了一种机制,用于以指定的时间间隔在线程池线程上执行方法
您也可以使用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
这个任务。
由于CancellationTokenSource
和Task
都实现了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
而不是检查令牌。这将引发您必须捕捉的异常