在主线程中定期运行任务而不阻塞 C#

huangapple go评论70阅读模式
英文:

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&lt;bool&gt;(
        () =&gt; 
        {
            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 () =&gt;
{
    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 () =&gt;
{
    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
}

huangapple
  • 本文由 发表于 2023年2月16日 02:16:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/75463916.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定