Home > Mobile >  LINQ's Select inside Task.Run skipping first value whereas ForEach does not
LINQ's Select inside Task.Run skipping first value whereas ForEach does not

Time:08-17

I have a function

public Task<IEnumerable<T>> GetVersionsAsync<T>(string key)
{
    var request = GetVersionsRequestBuilder.Create(_databaseName, _collectionName).WithKey(key);

    return Task.Run(() =>
    {
        var records = _myReader.GetAllVersionsForKey(request);        

        var returnValue = records.Select(record =>
        {
            var document = _mySerializer.Deserialize<T>(record.document);
            return document;
        });

        return returnValue;
    });
}

This is skipping first value and giving all rest results

However, if I change the LINQ part to ForEach, then it gives all records

var returnValue = new List<T>();

foreach (var record in records)
{
    var document = _mySerializer.Deserialize<T>(record.document);
    returnValue.Add(document);
}

return returnValue as IEnumerable<T>;

Is this due to LINQ's Select projection being inside Task.Run?

CodePudding user response:

Do not mix IEnumerables and Tasks. Deferred Execution and parallel execution at the same time are really not playing well together.

I don't know what specifically trigger your weird behavior, but generally speaking, your task is useless, because it does not do what you think it does. It does not deserialize all elements. The task is done almost immediately, and it returns the state-machine that is your IEnumerable. Only when you materialize it, after your task is done, it will actually do what you intend to do with the LinQ.

So, do not mix it. If you want it to run async, do so, but materialize it:

public Task<List<T>> GetVersionsAsync<T>(string key)
{
    var request = GetVersionsRequestBuilder.Create(_databaseName, _collectionName).WithKey(key);

    return Task.Run(() =>
    {
        var records = _myReader.GetAllVersionsForKey(request);        

        var returnValue = records.Select(
            record => _mySerializer.Deserialize<T>(record.document)
        ).ToList(); // materialize it

        return returnValue;
    });
}

What you propbably should do is make the method async and call the async version of your database reader instead of faking it.

CodePudding user response:

I don't disagree with Nvoigt's advice but I ran two dummy versions of the code. The first version still uses IEnumerable and the second materializes into the List type. I got the same result with each which is that all the results were returned as expected. I have to conclude then that the issue is inside of the Deserialize call.

V1:

    static async Task Main()
    {
        var insideTask = await GetFruitsAsync();
        foreach (var obj in insideTask)
        {
            Console.WriteLine("{0}", obj);
        }
        return;
    }

    public static Task<IEnumerable<string>> GetFruitsAsync()
    {
        IEnumerable<string> fruits = new string[] { "apple", "banana", "mango", "orange",
                  "passionfruit", "grape" };

        return Task.Run(() =>
        {
            var query = fruits.Select(fruit =>
            {
                var returnValue = fruit.Substring(0, 2);
                return returnValue;
            });
            return query;
        });
    }

V2:

    static async Task Main()
    {
        var insideTask = await GetFruitsAsync();
        foreach (var obj in insideTask)
        {
            Console.WriteLine("{0}", obj);
        }
        return;
    }

    public static Task<List<string>> GetFruitsAsync()
    {
        List<string> fruits = new List<string> { "apple", "banana", "mango", "orange",
                  "passionfruit", "grape" };

        return Task.Run(() =>
        {
            var query = fruits.Select(fruit =>
            {
                var returnValue = fruit.Substring(0, 2);
                return returnValue;
            });
            return query.ToList();
        });
    }
  • Related