Waiting without Sleeping?

5.1k views Asked by At

What I'm trying to do is start a function, then change a bool to false, wait a second and turn it to true again. However I'd like to do it without the function having to wait, how do I do this?

I can only use Visual C# 2010 Express.

This is the problematic code. I am trying receive user input (right arrow for example) and move accordingly, but not allow further input while the character is moving.

        x = Test.Location.X;
        y = Test.Location.Y;
        if (direction == "right") 
        {
            for (int i = 0; i < 32; i++)
            {
                x++;
                Test.Location = new Point(x, y);
                Thread.Sleep(31);
            }
        }
    }

    private void Form1_KeyDown(object sender, KeyEventArgs e)
    {
        int xmax = Screen.PrimaryScreen.Bounds.Width - 32;
        int ymax = Screen.PrimaryScreen.Bounds.Height - 32;
        if (e.KeyCode == Keys.Right && x < xmax) direction = "right";
        else if (e.KeyCode == Keys.Left && x > 0) direction = "left";
        else if (e.KeyCode == Keys.Up && y > 0) direction = "up";
        else if (e.KeyCode == Keys.Down && y < ymax) direction = "down";

        if (moveAllowed)
        {
            moveAllowed = false;
            Movement();
        }
        moveAllowed = true;  
    }
2

There are 2 answers

7
ixSci On BEST ANSWER

Use Task.Delay:

Task.Delay(1000).ContinueWith((t) => Console.WriteLine("I'm done"));

or

await Task.Delay(1000);
Console.WriteLine("I'm done");

For the older frameworks you can use the following:

var timer = new System.Timers.Timer(1000);
timer.Elapsed += delegate { Console.WriteLine("I'm done"); };
timer.AutoReset = false;
timer.Start();

Example according to the description in the question:

class SimpleClass
{
    public bool Flag { get; set; }

    public void function()
    {
        Flag = false;
        var timer = new System.Timers.Timer(1000);
        timer.Elapsed += (src, args) => { Flag = true; Console.WriteLine("I'm done"); };
        timer.AutoReset = false;
        timer.Start();
    }
}
2
sstan On

I realize an answer has already been accepted, and I do like ixSci's answer where he recommends the use of a Timer object to accomplish OP's goal.

However, using System.Timers.Timer specifically introduces threading considerations. And to ensure correctness in this case, more code is required to properly synchronize the boolean flag value. Basically, anywhere where the flag is read or written, the code region would need to have a lock statement defined around it.

It would have to look something like this:

private final object flagLock = new object();
private bool moveAllowed = true;
private System.Timers.Timer timer = new System.Timers.Timer();

public Form1()
{
    this.timer.Interval = 1000;
    this.timer.AutoReset = false;
    this.timer.Elapsed += (s, e) =>
    {
        // this DOES NOT run on the UI thread, so locking IS necessary to ensure correct behavior.
        this.timer.Stop();
        lock (this.flagLock) {
            this.moveAllowed = true;
        }
    };
}

// The code in this event handler runs on the UI thread.
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    // Locking is necessary here too.
    lock (this.flagLock) {
        if (this.moveAllowed)
        {
            this.moveAllowed = false;
            Movement();
            this.timer.Start(); // wait 1 second to reset this.moveAllowed to true.
        }
    }
}

Alternatively, to avoid having to think about threads, perhaps OP could consider using a different flavor of the Timer class. Namely: System.Windows.Forms.Timer. This way, the boolean flag will always be read/written on the UI thread, and no extra locking of any sort is required to ensure correctness.

In this case, the code would look something like this:

private bool moveAllowed = true;
private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();

public Form1()
{
    this.timer.Interval = 1000;
    this.timer.Tick += (s, e) =>
    {
        // this runs on the UI thread, so no locking necessary.
        this.timer.Stop(); // this call is necessary, because unlike System.Timers.Timer, there is no AutoReset property to do it automatically.
        this.moveAllowed = true;
    };
}

// The code in this event handler runs on the UI thread.
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    if (this.moveAllowed)
    {
        this.moveAllowed = false;
        Movement();
        this.timer.Start(); // wait 1 second to reset this.moveAllowed to true.
    }
}