开发者

How do I properly queue and execute tasks with variable start times in c#?

I'm still new to C#. Currently using .NET 3.5. I'm writing an online game, and my game engine needs to handle player requested unit movement. The units will move at different speeds and can have different destinations, so the actual time that the unit arrives (and the command completes) will differ widely.

My strategy is that the model will calculate the next move along a given path, and then queue a worker to execute around the same time the unit would be arriving at the location. The worker will update the unit, notify players, queue the next step, etc.

One of my biggest stumbling blocks was writing a worker thread that would conditionall开发者_运维问答y execute a task. In the past my worker threads have just tried to run each task, in order, as quickly as possible. The other challenge is that unit moves might get queued that have earlier start times than any other task in the queue, so obviously, they would need to get moved to the front of the line.

This is what I have figured out so far, it works but it has some limitations, namely its 100ms sleep limits my command response time. Is there a pattern for what i'm trying to do, or am I missing something obvious?

Thank you!

public class ScheduledTaskWorker
{
    List<ScheduledTask> Tasks = new List<ScheduledTask>();

    public void AddTask(ScheduledTask task)
    {
        lock (Tasks)
        {
            Tasks.Add(task);
            Tasks.Sort();
        }
    }

    private void RemoveTask(ScheduledTask task)
    {
        lock (Tasks)
        {
            Tasks.Remove(task);
        }
    }

    private ScheduledTask[] CopyTasks()
    {
        lock (Tasks)
        {
            return Tasks.ToArray();
        }
    }


    public void DoWork()
    {
        while (!StopWorking)
        {
            ScheduledTask[] safeCopy = CopyTasks();

            foreach (ScheduledTask task in safeCopy)
            {
                if (task.ExecutionTime > DateTime.Now) 
                    break;

                if (ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadPoolCallBack), task))
                    RemoveTask(task);
                else
                {
                    // if work item couldn't be queued, stop queuing and sleep for a bit
                    break;
                }
            }

            // sleep for 100 msec so we don't hog the CPU. 
            System.Threading.Thread.Sleep(100);
        }
    }

    static void ThreadPoolCallBack(Object stateInfo)
    {
        ScheduledTask task = stateInfo as ScheduledTask;
        task.Execute();
    }

    public void RequestStop()
    {
        StopWorking = true;
    }

    private volatile bool StopWorking;
}

And the task itself...

public class ScheduledTask : IComparable
{
    Action<Item> Work;
    public DateTime ExecutionTime;
    Item TheItem;

    public ScheduledTask(Item item, Action<Item> work, DateTime executionTime)
    {
        Work = work;
        TheItem = item;
        ExecutionTime = executionTime;
    }

    public void Execute()
    {
        Work(TheItem);
    }

    public int CompareTo(object obj)
    {
        ScheduledTask p2 = obj as ScheduledTask;
        return ExecutionTime.CompareTo(p2.ExecutionTime);
    }

    public override bool Equals(object obj)
    {
        ScheduledTask p2 = obj as ScheduledTask;
        return ExecutionTime.Equals(p2.ExecutionTime);
    }

    public override int GetHashCode()
    {
        return ExecutionTime.GetHashCode();
    }
}


I'm not disagreeing with Chris, his answer is probably more appropriate to your underlying problem.

However, to answer your specific question, you could use something equivalent to Java's DelayQueue, which looks to have been ported to C# here: http://code.google.com/p/netconcurrent/source/browse/trunk/src/Spring/Spring.Threading/Threading/Collections/Generic/DelayQueue.cs?spec=svn16&r=16

Your Task class would implement IDelayed then you could either have a single thread calling Take (which blocks while there are no items ready) and executing task, or pass them off to ThreadPool.QueueUserWorkItem.


That sounds dangerously brittle. Without knowing more about your game and its circumstances, it sounds like you could potentially drown the computer in new threads just by moving a lot of units around and having them do long-running tasks. If this is server-side code and any number of players could be using this at once, that almost guarantees issues with too many threads. Additionally, there are timing problems. Complex code, bad estimating, delays in starting a given thread (which will happen with the ThreadPool) or a slowdown from a lot of threads executing simultaneously will all potentially delay the time when your task is actually executed.

As far as I know, the majority of games are single-threaded. This is to cut down on these issues, as well as get everything more in sync. If actions happen on separate threads, they may receive unfair prioritization and run unpredictably, thereby making the game unfair. Generally, a single game loop goes through all of the actions and performs them in a time-based fashion (i.e. Unit Bob can move 1 square per second and his sprite animates at 15 frames per second). For example, drawing images/animating, moving units, performing collision detection and running tasks would all potentially happen each time through the loop. This eliminates timing issues as well, because everything will get the same priority (regardless of what they're doing or how fast they move). This also means that the unit will know the instant that it arrives at its destination and can execute whatever task it's supposed to do, a little bit at a time each iteration.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜