【问题标题】:Random tasks from Task.Factory.StartNew never finishes来自 Task.Factory.StartNew 的随机任务永远不会完成
【发布时间】:2015-12-29 11:59:22
【问题描述】:

我正在使用带有 Task.Factory 方法的异步等待。

public async Task<JobDto> ProcessJob(JobDto jobTask)
{
    try
    {
        var T = Task.Factory.StartNew(() =>
        {
            JobWorker jobWorker = new JobWorker();
            jobWorker.Execute(jobTask);
        });

        await T;
    }

我在这样的循环中调用这个方法

for(int i=0; i < jobList.Count(); i++)
{
    tasks[i] = ProcessJob(jobList[i]);
}

我注意到新任务在 Process explorer 中打开并且它们也开始工作(基于日志文件)。但是在 10 次中,有时 8 次或有时 7 次完成。他们中的其他人永远不会回来。

  1. 为什么会这样?
  2. 它们是否超时?我可以在哪里为我的任务设置超时?

更新

基本上,我希望每个任务在被调用后立即开始运行,并等待 AWAIT T 关键字的响应。我在这里假设一旦他们完成,他们每个人都会回到 Await T 并执行下一个动作。我已经在 10 个任务中有 7 个看到了这个结果,但其中 3 个没有回来。

谢谢

【问题讨论】:

  • 你还在等他们吗?有没有抛出异常?
  • 在你的 for 循环之后你尝试Task.WaitAll(tasks) 了吗?你不是在等待你的tasks 完成。
  • @Ned 我正在等待使用 await 关键字......
  • @ArghyaC 我不想执行 WaitAll,因为我必须等待所有任务完成才能执行下一步操作。我不想阻止呼叫....基本上我希望每个任务开始运行并立即执行他们的事情。一旦他们完成了,就回来并在 Await T 字上向我报告
  • 你真的 should notawaitTaskFactory.StartNew 结合使用,请改用 Task.Run(

标签: c# .net async-await task threadpool


【解决方案1】:

如果没有其他代码,很难说问题出在哪里,但是您可以通过使 ProcessJob 同步然后调用 Task.Run 来简化代码。

public JobDto ProcessJob(JobDto jobTask)
{
    JobWorker jobWorker = new JobWorker();
    return jobWorker.Execute(jobTask);
}

启动任务并等待所有任务完成。更喜欢使用Task.Run 而不是Task.Factory.StartNew,因为它为将工作推到后台提供了更有利的默认值。见here

for(int i=0; i < jobList.Count(); i++)
{
    tasks[i] = Task.Run(() => ProcessJob(jobList[i]));
}

try
{
   await Task.WhenAll(tasks);
}
catch(Exception ex)
{
   // handle exception
}

【讨论】:

  • 我不想阻止通话,所以我不做WhenAll...我基本上是在启动任务,但Task.Run并在等待
【解决方案2】:

首先,让我们制作一个可重现的代码版本。这不是实现您正在做的事情的最佳方式,而是向您展示代码中正在发生的事情!

我将保持代码与您的代码几乎相同,除了我将使用简单的 int 而不是您的 JobDto 并且在完成工作后 Execute() 我将写入一个我们可以验证的文件之后。这是代码

public class SomeMainClass
{
    public void StartProcessing()
    {
        var jobList = Enumerable.Range(1, 10).ToArray();
        var tasks = new Task[10];
        //[1] start 10 jobs, one-by-one
        for (int i = 0; i < jobList.Count(); i++)
        {
            tasks[i] = ProcessJob(jobList[i]);
        }
        //[4] here we have 10 awaitable Task in tasks
        //[5] do all other unrelated operations
        Thread.Sleep(1500); //assume it works for 1.5 sec
        // Task.WaitAll(tasks); //[6] wait for tasks to complete
        // The PROCESS IS COMPLETE here
    }

    public async Task ProcessJob(int jobTask)
    {
        try
        {
            //[2] start job in a ThreadPool, Background thread
            var T = Task.Factory.StartNew(() =>
            {
                JobWorker jobWorker = new JobWorker();
                jobWorker.Execute(jobTask);
            });
            //[3] await here will keep context of calling thread
            await T; //... and release the calling thread
        }
        catch (Exception) { /*handle*/ }
    }
}

public class JobWorker
{
    static object locker = new object();
    const string _file = @"C:\YourDirectory\out.txt";
    public void Execute(int jobTask) //on complete, writes in file
    {
        Thread.Sleep(500); //let's assume does something for 0.5 sec
        lock(locker)
        {
            File.AppendAllText(_file, 
                Environment.NewLine + "Writing the value-" + jobTask);
        }
    }
}

只运行StartProcessing() 之后,这就是我在文件中得到的内容

Writing the value-4
Writing the value-2
Writing the value-3
Writing the value-1
Writing the value-6
Writing the value-7
Writing the value-8
Writing the value-5

所以,8/10 的工作已经完成。显然,每次运行时,数量和顺序都可能发生变化。但是,关键是,所有的工作都没有完成!

现在,如果我取消注释步骤 [6] Task.WaitAll(tasks);,这就是我在文件中得到的内容

Writing the value-2
Writing the value-3
Writing the value-4
Writing the value-1
Writing the value-5
Writing the value-7
Writing the value-8
Writing the value-6
Writing the value-9
Writing the value-10

所以,我所有的工作都在这里完成了!

为什么代码会这样,已经在 code-cmets 中解释过了。需要注意的主要事项是,您的任务在基于Background 的线程中运行。所以,如果你不等待它们,它们将在 MAIN 进程结束并且主线程退出时被杀死!

如果您仍然不想在那里等待任务,您可以从第一个方法返回任务列表,并在流程结束时返回 await 任务,类似这样

public Task[] StartProcessing()
{
    ...
    for (int i = 0; i < jobList.Count(); i++)
    {
        tasks[i] = ProcessJob(jobList[i]);
    }
    ...
    return tasks;
}

//in the MAIN METHOD of your application/process
var tasks = new SomeMainClass().StartProcessing();
// do all other stuffs here, and just at the end of process
Task.WaitAll(tasks);

希望这能消除所有困惑。

【讨论】:

  • 嗨,很好的解释......我的问题是,如果我执行 Task.Waital,我的循环将不得不等待。整个代码在带有计时器的 Windows 服务中运行,所以我不想阻止所有任务完成。这可能是我设计中的一个问题。想法??
  • ....看起来我正在进行计时器事件,因此它会回收旧线程...这将导致我的等待任务丢失...正确吗?
  • 如果@StephenCleary 可以在这里提供更好的方法和/或解释,那就太好了:)
  • @Zeus 很难在没有看到您的实际代码的情况下发表评论,但是鉴于调用线程没有退出或没有足够的时间,任务应该完成。检查是否可以在最后给出的替代代码行中做某事。
【解决方案3】:

您的代码可能正在吞噬异常。我将在启动新任务的代码部分的末尾添加一个ContineWith 调用。像这样未经测试的代码:

var T = Task.Factory.StartNew(() =>
        {
            JobWorker jobWorker = new JobWorker();
            jobWorker.Execute(jobTask);
        }).ContinueWith(tsk =>
        {
            var flattenedException = tsk.Exception.Flatten();
            Console.Log("Exception! " + flattenedException);
            return true;
         });
        },TaskContinuationOptions.OnlyOnFaulted);  //Only call if task is faulted

另一种可能性是其中一个任务中的某些内容超时(如您提到的)或死锁。要追踪超时(或者可能是死锁)是否是根本原因,您可以添加一些超时逻辑(如this SO 回答中所述):

int timeout = 1000; //set to something much greater than the time it should take your task to complete (at least for testing)
var task = TheMethodWhichWrapsYourAsyncLogic(cancellationToken);
if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
{
    // Task completed within timeout.
    // Consider that the task may have faulted or been canceled.
    // We re-await the task so that any exceptions/cancellation is rethrown.
    await task;

}
else
{
    // timeout/cancellation logic
}

查看 MSDN 上 TPL 中有关异常处理的 documentation

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多