【问题标题】:What is the best practice for running multiple background tasks运行多个后台任务的最佳做法是什么
【发布时间】:2015-10-01 11:22:22
【问题描述】:

我有一个 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);
    }
}

场景 2:

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

【问题讨论】:

  • 将返回的任务分配到私有领域以感谢处置不是更好吗?
  • 你可以使用 Task.Run(() => doStuff("hello world"));而不是 Task.Factory.StartNew()
  • @AvsenevSlava 据我所知,他们是平等的。
  • @SerG 我相信取消令牌可以用来结束任务。
  • 但任务不会立即结束。那么异常呢?

标签: c# async-await task-parallel-library task


【解决方案1】:

async 方法同步运行,直到第一个 await。之后它将在 ThreadPool 线程上运行(除非有 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) 获得什么?
  • @Mohammadreza 是的,OnStop。如果您真的可以将其更改为异步方法并返回任务,那就太好了,但是您不能更改签名,因为它是覆盖。让它async void 会更糟。
  • 澄清一下,如果我将基类 void OnStop() 更改为 Task OnStop() 而没有 async 关键字,那么从基类继承的类可以使方法异步。我说的对吗?
  • @Mohammadreza 是的。我只是假设基类是.Net 的ServiceBase,您无法更改。
  • 是的。我只是出于好奇才问这个:)
【解决方案2】:

我无法解释哪个是最好的,但这是工作原理

在 1. 场景代码直到 await 关键字由父线程执行,即应用程序的主线程。因此,一旦执行等待任务执行完成,由保存的上下文处理的事情,即主线程上下文。

在 2. 场景代码中,它开始在任务工厂创建的线程上运行。这里一旦执行等待任务执行完成了父级处理的事情,即任务工厂创建的线程。

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

【讨论】:

    猜你喜欢
    • 2017-02-15
    • 1970-01-01
    • 2022-12-06
    • 1970-01-01
    • 2020-09-21
    • 1970-01-01
    • 2019-06-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多