任务在等待时标记为 RanToComplete,但仍在运行

本文关键字:运行 RanToComplete 在等待 记为 任务 | 更新日期: 2023-09-27 18:37:28

我仍然在跟上异步和多线程的速度。我正在尝试监视我启动的任务何时仍在运行(以在 UI 中显示)。 但是,它表明它比我想要的更早是 RanToCompletion,当它进入等待状态时,即使我认为它的状态仍在运行。

这是我正在做的示例。 这一切似乎都围绕着等待者。 当它命中等待时,它被标记为 RanToCompletion。

我想跟踪启动所有内容的主要任务,在某种程度上向我表明它仍然一直运行到最后,并且只有在完成所有操作时才能运行到 RanToComplete,包括 repo 调用和 WhenAll。

如何更改此设置以获取有关 tskProdSeeding 任务状态的反馈?

我的控制台应用程序 Main 方法调用以下内容:

Task tskProdSeeding;
tskProdSeeding = Task.Factory.StartNew(SeedingProd, _cts.Token);

运行这个:

private async void SeedingProd(object state)
{
    var token = (CancellationToken)state;
    while (!token.IsCancellationRequested)
    {
        int totalSeeded = 0;
        var codesToSeed = await _myRepository.All().ToListAsync(token);
        await Task.WhenAll(Task.Run(async () =>
        {
            foreach (var code in codesToSeed)
            {
                if (!token.IsCancellationRequested)
                {
                    try
                    {
                        int seedCountByCode = await _myManager.SeedDataFromLive(code);
                        totalSeeded += seedCountByCode;
                    }
                    catch (Exception ex)
                    {
                        _logger.InfoFormat(ex.ToString());
                    }
                }
            }
        }, token));
        Thread.Sleep(30000);
    }
}

任务在等待时标记为 RanToComplete,但仍在运行

如果使用async void则外部任务无法判断任务何时完成,则需要改用async Task

其次,一旦你切换到async TaskTask.Factory.StartNew无法处理返回Task的函数,你需要切换到Task.Run(

tskProdSeeding = Task.Run(() => SeedingProd(_cts.Token), _cts.Token);

完成这两项更改后,您将能够等待或.Wait() tskProdSeeding并且它会正确等待所有工作完成后再继续。

请阅读"异步/等待 - 异步编程中的最佳实践"以了解有关不执行async void的更多信息。

请阅读"StartNew is Dangerous",以了解有关为什么您不应该以使用StartNew的方式的更多信息。


附言SeedingProd您应该将其切换为使用 Thread.Sleep(30000); 的插入await Task.Delay(30000);,然后您将不会在等待线程时捆绑线程。如果您这样做,您可能会删除

tskProdSeeding = Task.Run(() => SeedingProd(_cts.Token), _cts.Token);

然后就去做

tskProdSeeding = SeedingProd(_cts.Token);

因为该函数内部不再有阻塞调用。

我不相信你根本不需要第二个线程(Task.RunStartNew)。看起来大部分工作都是 I/O 绑定的,如果您异步执行此操作并使用 Task.Delay 而不是 Thread.Sleep ,那么这些操作不会消耗线程,您的 UI 不应冻结。任何刚接触异步的人都需要了解的第一件事是,它与多线程不是一回事。后者是消耗更多的线程,前者是消耗更少的线程。专注于消除阻塞,您不需要第二个线程。

正如其他人所指出的,SeedingProd需要返回一个Task,而不是void,所以你可以观察它的完成。我相信你的方法可以简化为:

private async Task SeedingProd(CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        int totalSeeded = 0;
        var codesToSeed = await _myRepository.All().ToListAsync(token);
        foreach (var code in codesToSeed)
        {
            if (token.IsCancellationRequested)
                return;
            try
            {
                int seedCountByCode = await _myManager.SeedDataFromLive(code);
                totalSeeded += seedCountByCode;
            }
            catch (Exception ex)
            {
                _logger.InfoFormat(ex.ToString());
            }
        }
        await Task.Dealy(30000);
    }
}

然后只需调用该方法,无需等待它,您就会有您的任务。

Task mainTask = SeedingProd(token);

当您在方法上指定async时,它会编译到带有 Task 的状态机中,因此SeedingProd不会同步运行,而是充当 Task 即使返回 void 。因此,当你调用Task.Factory.StartNew(SeedingProd)时,你开始了一个启动另一个任务的任务 - 这就是为什么第一个任务在第二个任务之前立即完成。您所要做的就是添加 Task 返回参数而不是 void

private async Task SeedingProdAsync(CancellationToken ct)
{
...
}

并像这样简单地称呼它:

Task tskProdSeeding = SeedingProdAsync(_cts.Token);