英文:
Running a task periodically without blocking the main thread C#
问题
我正在尝试每30分钟发送一个保持连接的HTTP请求。然而,在此期间我还有其他需要调用的方法,所以我试图做的是:
Task.Factory.StartNew(async () =>
{
while(true){
await Task.Delay(new TimeSpan(0,30,0), CancellationToken.None);
await FooTask();
}
});
我使用得正确吗?
英文:
I am trying to send a keep-alive HTTP request each 30 minutes. However I have other methods to be called at the time in between, so what I tried to do is:
Task.Factory.StartNew(async () =>
{
while(true){
await Task.Delay(new TimeSpan(0,30,0), CancellationToken.None);
await FooTask();
}
});
Am I using it properly?
答案1
得分: 1
你是否正确地做了?不是。你说你想要一个循环,但没有写循环。你还在使用错误的任务创建函数:
Task.Run(async () =>
{
while(true)
{
await FooTask().ConfigureAwait(false);
await Task.Delay(new TimeSpan(0, 30, 0)).ConfigureAwait(false);
}
});
还有一个 PeriodicTimer
,它遵循类似的模式,你执行你的操作并 await
下一个时刻。
英文:
Are you doing it properly? No. You say you want a loop, but don't write a loop. You're also using the wrong task creation function:
Task.Run(async () =>
{
while(true)
{
await FooTask().ConfigureAwait(false);
await Task.Delay(new TimeSpan(0, 30, 0)).ConfigureAwait(false);
}
});
There's also PeriodicTimer
which follows a similar pattern, you do your action and await
the next tick.
答案2
得分: 0
我建议使用微软的响应式框架。
然后你可以这样做:
IDisposable subscription =
Observable
.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(30.0))
.SelectMany(_ => Observable.FromAsync(() => FooTask()))
.Subscribe();
调用 subscription.Dispose()
可以关闭它。非常简单明了。
英文:
I'd suggest using Microsoft's Reactive Framework.
Then you can just do this:
IDisposable subscription =
Observable
.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(30.0))
.SelectMany(_ => Observable.FromAsync(() => FooTask()))
.Subscribe();
Calling subscription.Dispose()
shuts it down. It's nice and simple.
答案3
得分: 0
我的理解是 Task.Run()
实际上是“发射并忘记”。
如果你尝试等待 Task.Run()
完成,它几乎会立即返回,因为它的“任务”是启动内部操作。一旦它完成了这个任务,它的 IsCompleted
。它不会挂钩到内部操作。
所以假设你有三个长时间运行的子任务,JobA()、JobB() 和 JobC(),需要按顺序完成...
public bool ExecuteLongRunningTasks(CancellationToken cancelToken = default)
{
if (!_JobACompleted)
{
JobA();
_JobACompleted = true;
if (cancelToken.IsCancellationRequested)
return false;
}
if (!_JobBCompleted)
{
JobB();
_JobBCompleted = true;
if (cancelToken.IsCancellationRequested)
return false;
}
JobC();
_AllJobsCompleted = true;
return true;
}
...在某个时刻,UI 线程可能需要这三个子任务的结果(这些任务已经被异步启动),那么合理的做法是允许长时间运行的子任务异步完成,然后让性能更好的 UI 线程完成未完成的子任务。
为了实现这一点,我必须使用 StartNew()
进行异步操作,因为 StartNew()
,“默认情况下”,会挂钩到它正在启动的操作...
public bool AsyncExecute()
{
// 属性访问器执行一些合理性检查。
if (!ClearToLoadAsync)
return false;
_AsyncActive = true;
_AsyncTask = Task.Factory.StartNew<bool>(
() =>
{
ExecuteLongRunningTasks(_CancelToken);
_AsyncActive = false;
return true;
},
default, TaskCreationOptions.LongRunning, TaskScheduler.Default);
return true;
}
...如果 UI 线程需要结果,它会使用备受诟病的 Wait()
...
public bool SyncExecute()
{
// 只是执行一些微不足道的死锁和完成检查的属性访问器。
if (!ClearToLoadSync)
return false;
if (_AsyncActive)
{
// 发送取消请求并等待。
_CancelTokenSource.Cancel();
_AsyncTask.Wait();
}
if (!_AllJobsCompleted)
{
// 同步执行任何未完成的长时间运行任务。
return ExecuteLongRunningTasks();
}
return true;
}
如果有人有通过 Task.Run()
实现相同目的的解决方案,我很乐意看看它是如何完成的,因为我之前尝试过但没有成功。
英文:
My understanding is that Task.Run()
is effectively fire and forget.
If you try to 'wait' for a Task.Run()
to complete it will return almost immediately because it's 'task' is to launch the inner operation.</br>
Once it's done that it's IsCompleted
. It does not hook onto the inner operation.</br>
So suppose you have 3 long-running sub-tasks, JobA(), JobB() and JobC(), to be completed sequentially...
public bool ExecuteLongRunningTasks(CancellationToken cancelToken = default)
{
if (!_JobACompleted)
{
JobA();
_JobACompleted = true;
if (cancelToken.IsCancellationRequested)
return false;
}
if (!_JobBCompleted)
{
JobB();
_JobBCompleted = true;
if (cancelToken.IsCancellationRequested)
return false;
}
JobC();
_AllJobsCompleted = true;
return true;
}
... and that at at some point the UI thread may require the results of these 3 sub-tasks (which have been launched asynchronously), then it stands to reason that you will allow a long running sub-task to complete asynchronously, and then have the better performing UI thread complete the uncompleted sub-tasks.
To achieve this I have to use StartNew()
for the asynchronous operations because StartNew(), 'by default', hooks onto the operation it is launching...
public bool AsyncExecute()
{
// Property accessor that does a few sanity checks.
if (!ClearToLoadAsync)
return false;
_AsyncActive = true;
_AsyncTask = Task.Factory.StartNew<bool>(
() =>
{
ExecuteLongRunningTasks(_CancelToken);
_AsyncActive = false;
return true;
},
default, TaskCreationOptions.LongRunning, TaskScheduler.Default);
return true;
}
... and if the UI thread needs the results it uses the much maligned Wait()
...
public bool SyncExecute()
{
// Just a property accessor that does some trivial deadlock and completion checks.
if (!ClearToLoadSync)
return false;
if (_AsyncActive)
{
// Post a cancel request and wait.
_CancelTokenSource.Cancel();
_AsyncTask.Wait();
}
if (!_AllJobsCompleted)
{
// Execute any outstanding long running tasks synchronously.
return ExecuteLongRunningTasks();
}
return true;
}
If anyone has a solution that achieves the same with Task.Run() I would love to see how it is done because I was unsuccessful.
答案4
得分: -1
你可能想使用 Task.Run()
而不是 Task.Factory.StartNewAsync()
,根据文档中的备注。除非你需要 Task.Factory.StartNewAsync()
中的额外选项(例如:任务创建选项、任务调度器、Task.Run()
不涵盖的参数传递)推荐使用 Task.Run()
。
Task.Run()
将任务排入当前线程池以运行,同时它还会返回一个任务的句柄,以便你可以等待完成或取消任务,如下例所示。
CancellationTokenSource src = new CancellationTokenSource();
CancellationToken ct = src.Token;
Task t = Task.Run(async () =>
{
while(true){
ct.ThrowIfCancellationRequested();
await Task.Delay(new TimeSpan(0,30,0), ct);
await FooTask();
}
}, ct);
// 在你的任务运行时做一些其他事情
// 取消你的任务,或者
ct.Cancel();
// 等待你的任务完成
try
{
t.Wait();
}
catch (AggregateException e)
{
// 处理异常
}
英文:
You likely want to use Task.Run()
instead of Task.Factory.StartNewAsync()
, per the remarks in the docs. Task.Run()
is recommended unless you need the extra options in Task.Factory.StartNewAsync()
(ex: Task Creation Options, Task Scheduler, Parameter Passing that Task.Run()
does not cover).
Task.Run()
will queue the task to run on the current thread pool, and it will also return a handle to the task in case you want to Wait for completion or Cancel the task, as shown in the below example.
CancellationTokenSource src = new CancellationTokenSource();
CancellationToken ct = src.Token;
Task t = Task.Run(async () =>
{
while(true){
ct.ThrowIfCancellationRequested();
await Task.Delay(new TimeSpan(0,30,0), ct);
await FooTask();
}
}, ct);
// Do some other stuff while your task runs
// Cancel Your Task, OR
ct.Cancel();
// Wait for your task to finish
try
{
t.Wait();
}
catch (AggregateException e)
{
// Handle the exception
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论