【问题标题】:Task being marked as RanToCompletion at await, when still Running任务在等待时被标记为 RanToCompletion,但仍在运行
【发布时间】:2016-06-18 11:11:19
【问题描述】:

我仍在学习异步和多线程。我正在尝试监视我开始的任务何时仍在运行(以显示在 UI 中)。但是,它表明它比我想要的更早 RanToCompletion,当它遇到等待时,即使我认为它的状态仍在运行。

这是我正在做的示例。这一切似乎都以等待为中心。当它遇到等待时,它会被标记为 RanToCompletion。

我想跟踪启动这一切的主任务,以向我表明它仍在运行一直到结束并且只有 RanToCompletion 完成后,包括 repo 调用和WhenAll。

如何更改此设置以获得我想要的关于 tskProdSeding 任务状态的反馈?

我的控制台应用程序主方法调用这个:

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

【问题讨论】:

    标签: c# asynchronous async-await


    【解决方案1】:

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

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

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

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

    请阅读“Async/Await - Best Practices in Asynchronous Programming”了解更多关于不做async void的信息。

    请阅读“StartNew is Dangerous”以详细了解为什么您不应该按照您的使用方式使用 StartNew。


    附:在SeedingProd 中,您应该将其切换为使用await Task.Delay(30000); insetad of Thread.Sleep(30000);,这样您就不会在等待时占用线程。如果你这样做,你可能会放弃

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

    然后就成功了

    tskProdSeeding = SeedingProd(_cts.Token);
    

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

    【讨论】:

      【解决方案2】:

      我根本不相信您需要第二个线程(Task.RunStartNew)。看起来大部分工作都是 I/O 绑定的,如果您异步执行它并使用 Task.Delay 而不是 Thread.Sleep,那么这些操作消耗的 there is no thread 并且您的 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);
      

      【讨论】:

        【解决方案3】:

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

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

        并像这样简单地调用它:

        Task tskProdSeeding = SeedingProdAsync(_cts.Token);
        

        【讨论】:

        • 异步函数同步运行,直到第一个 await 在您调用它时尚未完成。正因为如此,仅仅删除任务工厂是有风险的,他还有Thread.Sleep(30000);,如果ToListAsyncWhenAll同时完成,你会导致调用SeedingProdAsync的线程阻塞30秒,如果你是要放弃工厂(而不是用Taks.Run( 替换它),您必须将睡眠换成Task.Delay
        • 我敢打赌 Thread.Sleep 只是为了跟踪完成的测试而添加的,并不是真实代码的一部分
        • 我不这么认为,它在 while 循环内,我敢打赌,睡眠在那里是因为他希望任务每 30 秒运行一次,直到它被取消。
        • 或者切换到Task.Delay等待它。
        • 我认为 Task.Delay 更好,据我所知,“长时间运行”只是创建一个新线程。
        猜你喜欢
        • 1970-01-01
        • 2014-07-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多