运行多个后台任务的最佳实践是什么?

4

我有一个 Windows 服务(.NET 4.5.2),想在后台运行多个任务,同时使用 System.Threading.Tasks,您认为以下哪种实现方式最佳?或者我完全错了吗?

场景 1:

protected override void OnStart(string[] args)
{
    // Assume all tasks implemented the same way.
    // I believe we shouldn't await the tasks in this scenario.
    var token = this._cancellationTokenSource.Token;
    this.RunTask1(token);
    this.RunTask2(token);
    this.RunTask3(token);
}

private async Task RunTask1(CancellationToken token)
{
    var telebot = new Telebot("SOMETHING");
    while( true )
    {
        // Some work...
        // I/O dependent task.
        var response  = await telebot.GetUpdatesAsync(cancellationToken: token);

        //
        // Some other work
        // maybe some database calls using EF async operators.
        //
        await Task.Delay(TimeSpan.FromSeconds(1), token);
    }
}

场景二:
protected override void OnStart(string[] args)
{
    // Assume all tasks implemented the same way.
    // I believe we shouldn't await the tasks in this scenario.
    var token = this._cancellationTokenSource.Token;
    this.RunTask1(token);
    this.RunTask2(token);
    this.RunTask3(token);
}

private void RunTask1(CancellationToken token)
{
    Task.Factory.StartNew(async () =>
        {
            var telebot = new Telebot("SOMETHING");
            while( true )
            {
                // Some work...
                // I/O dependent task.
                var response = await telebot.GetUpdatesAsync(cancellationToken: token);

                //
                // Some other work
                // may be some database calls using EF async operators.
                //
                await Task.Delay(TimeSpan.FromSeconds(1), token);
            }
        }, token);
}

把返回的任务分配给私有字段以更好地处理,这样不是更好吗? - SerG
1
你可以使用 Task.Run(() => doStuff("hello world")); 代替 Task.Factory.StartNew()。 - slava
据我所知,它们是相等的。 - mrtaikandi
@SerG 我相信可以使用取消令牌来结束任务。 - mrtaikandi
正确,而且你也不应该结束它们,我认为这就是取消令牌的作用所在。至于异常处理,每个任务都应该处理异常,但我没有在那里编写任何逻辑,因为我的问题是这种运行任务的方法是否好,或者是否存在任何陷阱。 - mrtaikandi
显示剩余3条评论
2个回答

2

我不能解释哪个是最好的,但以下是它们的工作原理

在第一种情况下,直到等待关键字执行完毕,代码由父线程即应用程序的主线程执行。因此,一旦执行等待任务完成,处理该事情的上下文将被保存,即主线程上下文。

在第二种情况下,代码开始在由任务工厂创建的线程上运行。在这里,一旦执行等待任务完成,事情就由父线程即由任务工厂创建的线程处理。

因此,如果您想将某些内容发布到主线程,特别是应用程序的UI,则第一种情况很好。如果您想在后台运行事物并且不需要父上下文,即主线程或UI线程,则第二种情况很好。


1
一个async方法在第一个await之前是同步运行的。之后它将在线程池线程上运行(除非有SynchronizationContext)。
因此,不建议使用Task.Factory.StartNewTask.Run,因为它试图并行化已经大部分并行化的内容。
然而,如果您有一个实质性的同步部分,使用Task.Run(比Task.Factory.StartNew更可取)进行并行化可能很有用,但您应该在调用方法时而不是在方法本身中执行它。
所以,“方案1”比“方案2”更好。
我认为您不应该点火并忘记这些操作。您应该存储任务,等待它们完成,并观察其中的任何异常,例如:
protected override void OnStart()
{
    var token = _cancellationTokenSource.Token;
    _tasks.Add(RunTask1(token));
    _tasks.Add(RunTask2(token));
    _tasks.Add(Task.Run(() => RunTask3(token))); // assuming RunTask3 has a long synchronous part
}

List<Task> _tasks;

protected override void OnStop()
{
    _cancellationTokenSource.Cancel();
    Task.WhenAll(_tasks).Wait();
}

谢谢。我假设第二个OnStart方法是OnStop。在这种情况下,通过使OnStop方法异步并使用await Task.WhenAll(_tasks),我是否能获得任何好处? - mrtaikandi
@Mohammadreza 是的,OnStop方法。如果你真的能将其改为异步方法并返回一个任务,那就太好了,但是你不能改变方法签名,因为它是一个重写方法。将其改为async void会更糟糕。 - i3arnon
只是为了澄清,如果我将基类的 void OnStop() 更改为 Task OnStop() 而没有 async 关键字,那么继承自基类的类可以使该方法异步。我是正确的吗? - mrtaikandi
@Mohammadreza 是的。我只是假设基类是 .Net 的 ServiceBase,而这是您无法更改的。 - i3arnon
是的,我只是出于好奇问了这个问题 :) - mrtaikandi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接