Strange Behavior When I Don't Use TaskCreation

2019-01-19 21:19发布

问题:

I have an engine that has an arbitrary number of pollers which each do their "poll" every few seconds. I want the pollers to run in different threads, but each "poll" within a single poller should be sequential so that one happens after the next. Everything is working using this code to start the polling process:

    public void StartPolling()
    {
        Stopwatch watch = new Stopwatch();
        while (Engine.IsRunning)
        {
            Task task = Task.Factory.StartNew(() =>{
                watch.Restart();
                Poll();
                watch.Stop();
            },TaskCreationOptions.LongRunning);
            task.Wait();
            if(Frequency > watch.Elapsed) Thread.Sleep(Frequency - watch.Elapsed);
        }
    }

It took me awhile, however, to discover the TaskCreationOptions.LongRunning option which solved a strange problem I was having that I still don't understand. Without that option, if I run a test that creates 1-3 of these pollers, everything worked fine. If I created 4+ then I ran into strange behavior. Three of the pollers would work, one would just perform one poll, and any remaining would not poll at all. It makes total sense that my tasks are long running. They are after all running the entire length of my program. But I don't understand why I would get some bad behavior without this option set. Any help would be appreciated.

回答1:

When you don't use the LongRunning flag, the task is scheduled on a threadpool thread, not its own (dedicated) thread. This is likely the cause of your behavioral change - when you're running without the LongRunning flag in place, you're probably getting threadpool starvation due to other threads in your process.

That being said, your above code doesn't really make a lot of sense. You're starting a dedicated thread (via Task....StartNew with LongRunning) to start a task, then immediately calling task.Wait(), which blocks the current thread. It would be better to just do this sequentially in the current thread:

public void StartPolling()
{
    Stopwatch watch = new Stopwatch();
    while (Engine.IsRunning)
    {
        watch.Restart();
        Poll();
        watch.Stop();
        if(Frequency > watch.Elapsed) Thread.Sleep(Frequency - watch.Elapsed);
    }
}


回答2:

TPL (and the traditional ThreadPool) limits the number of threads in the pool (typically a small multiple of the number of CPU cores, usually 2x cores). If you mark a task as LongRunning, it knows that the task won't finish soon and may not subject this task to the threads limit.

Without LongRunning, it assumes that you task will finish quickly (which it doesn't) so it stays within the threads limit. Then if you create more tasks than the threads limit and the running tasks never end, TPL stops all other tasks from running waiting in vain for those running tasks to finish (which they will never do).