Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why async functions are called twice?

I'm using Threading timer to do some periodic job:

private static async void TimerCallback(object state)
{
        if (Interlocked.CompareExchange(ref currentlyRunningTasksCount, 1, 0) != 0)
        {
            return;
        }

        var tasksRead = Enumerable.Range(3, 35).Select(i => ReadSensorsAsync(i));
        await Task.WhenAll(tasksRead);
        var tasksRecord = tasksRead.Where(x => x.Result != null).Select(x => RecordReadingAsync(x.Result));
        await Task.WhenAll(tasksRecord);

        Interlocked.Decrement(ref currentlyRunningTasksCount);
}

I made timer call back async and used WhenAll. In each working async function I have one Console output, which shows activity. Now the problem is that on second timer event each async function is working twice for some reason. The timer is set to long period. The application is Windows Console type. Is it Select that somehow make it run twice?

like image 695
Pablo Avatar asked Jun 04 '16 07:06

Pablo


People also ask

What happens when you call an async function?

Async functions will always return a value. It makes sure that a promise is returned and if it is not returned then JavaScript automatically wraps it in a promise which is resolved with its value. Example-1: javascript.

What is the purpose of asynchronous function?

Note: The purpose of async / await is to simplify the syntax necessary to consume promise-based APIs. The behavior of async / await is similar to combining generators and promises. Async functions always return a promise.

Why is async await better than then?

Async/await and then() are very similar. The difference is that in an async function, JavaScript will pause the function execution until the promise settles. With then() , the rest of the function will continue to execute but JavaScript won't execute the . then() callback until the promise settles.

Can async function have more than one await?

Using one try/catch block containing multiple await operations is fine when waiting for promises created on the right hand side of the await unary operator: The await operator stores its parent async functions' execution context and returns to the event loop.


2 Answers

This:

var tasksRead = Enumerable.Range(3, 35).Select(i => ReadSensorsAsync(i));

creates a lazily evaluated IEnumerable which maps numbers to method invocation results. ReadSensorsAsync is not invoked here, it will be invoked during evaluation.

This IEnumerable is evaluated twice. Here:

await Task.WhenAll(tasksRead);

and here:

// Here, another lazy IEnumerable is created based on tasksRead.
var tasksRecord = tasksRead.Where(...).Select(...);  
await Task.WhenAll(tasksRecord);  // Here, it is evaluated.

Thus, ReadSensorsAsync is invoked twice.


As csharpfolk suggested in the comments, materializing the IEnumerable should fix this:

var tasksRead = Enumerable.Range(3, 35).Select(i => ReadSensorsAsync(i)).ToList();
like image 194
Heinzi Avatar answered Sep 29 '22 22:09

Heinzi


When you use Task.WhenAll on a IEnumerable<Task<T>> it will return a T[] of the completed Tasks results. You need to save that variable and use it or else you will end up with the multiple enumerations like Henzi mentioned in his answer.

Here is a solution without the unnecessarily calling of .ToList()

private static async void TimerCallback(object state)
{
        if (Interlocked.CompareExchange(ref currentlyRunningTasksCount, 1, 0) != 0)
        {
            return;
        }

        var tasksRead = Enumerable.Range(3, 35).Select(i => ReadSensorsAsync(i));
        var finshedTasks = await Task.WhenAll(tasksRead);
        var tasksRecord = finshedTasks.Where(x => x != null).Select(x => RecordReadingAsync(x));
        await Task.WhenAll(tasksRecord);

        Interlocked.Decrement(ref currentlyRunningTasksCount);
}
like image 39
Scott Chamberlain Avatar answered Sep 29 '22 22:09

Scott Chamberlain