Alex Zaitsev
Alex Zaitsev

Reputation: 2766

Why does Thread.Sleep work but Task.Delay does not?

In my code I assume that outerFlag will be hit after innerFlag but it actually runs like fire and forget (innerFlag is hit after outerFlag). When I use Thread.Sleep instead of Task.Delay flags are hit in correct order.

Here is the code:

[Test]
public async Task Test() {

    bool innerFlag = false;
    bool outerFlag = false;

    await Lock(async () => {
        await Task.Delay(2000);
        innerFlag = true;
    });

    outerFlag = true;

    Thread.Sleep(1000);
    Thread.Sleep(2500);
}

private Task Lock(Action action) {
    return Task.Run(action);
}

I also noticed that when I call Task.Delay and set innerFlag without a Lock method but by direct lambda, it works as expected.

Can somebody explain such behaviour?

Upvotes: 0

Views: 241

Answers (1)

Theodor Zoulias
Theodor Zoulias

Reputation: 43384

Your Lock method doesn't understand async delegates, so the async delegate you are trying to pass as argument is treated as async void. Async voids are problematic in all sorts of ways and should be avoided, unless they are used for their intended purpose, as event handlers.

To ensure that your method understand async delegates you must create an overload that accepts a Func<Task> as argument:

private Task Lock(Func<Task> func)
{
    return Task.Run(func);
}

Notice that the func argument can be passed directly to Task.Run, because this method understands async delegates too. Not all built-in methods understand async delegates, with notable examples the Task.Factory.StartNew and Parallel.ForEach. You must be cautious every time you add the async modifier in a delegate. You must be sure that the called method understands async delegates, or else you may end up with async voids and the havoc they create.

Upvotes: 1

Related Questions